feat: include closure 跨ぎの型/関数共有と generation dist 導入 - #120
redpeacock78 wants to merge 20 commits into
Conversation
BSD 専用の mv -h に依存すると GNU CI で current symlink を切り替えられないため、実行時に mv -h を確認して未対応環境では mv -T に切り替える。相対 dist の index 出力も物理パスへ固定し、worktree の cwd 移動で失敗しないようにする。
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 773cecbc30
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| _rel="${_pub_rel[$i]}" | ||
| _stable="$dist/${namespace:+$namespace/}$_rel" | ||
| _compat="$dist/$_rel" | ||
| mkdir -p "$(dirname "$_stable")" "$(dirname "$_compat")" |
There was a problem hiding this comment.
Reject namespaced symlink parents before linking
When the namespaced output parent already exists as a symlink, this mkdir -p follows it because the earlier escape checks only validate the staging path and the compat parent. For example, from a project with dist/app -> /tmp/outside, running HAWK_DIST=dist hawk-libs desugar app/main.awk exits 0 and creates /tmp/outside/main.awk, reopening the dist-escape issue the pre-mkdir checks are meant to prevent. Validate the stable parent path before creating the stable symlink.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
対応しました。既存の親パス検査ロジックを _prepare_dist_parent に共通化し、.hawk-generations の作成・前世代の hardlink 継承先・desugar の出力先・stable/compat symlink の親、すべての mkdir 経路で dist からの相対で既存 symlink を検査してから作成するようにしています。テスト stable_symlink_parent_escape_rejected(dist/app が外部 symlink の状態で拒否され、外部にファイルが作られないこと)を追加しています。
| exit 1 | ||
| fi | ||
| if [[ ! -e "$_stable" && ! -L "$_stable" ]]; then | ||
| ln -s "$runtime_ns/$_rel" "$_stable" |
There was a problem hiding this comment.
Preserve older namespaces when moving current
These stable links are anchored to the global dist/current, but each new generation contains only the closure being published in this run. If one dist is used for multiple entries, desugaring apps/a/main.awk and then apps/b/main.awk switches current to a generation that lacks apps/a, leaving the previously returned dist/apps/a/main.awk symlink broken. Either carry forward previous generation contents or link each stable path to the generation that actually owns that file.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
対応しました。新世代を作る際、現在の dist/current が指す世代の全ファイルを(symlink ではなく実体の)hardlink で新世代ディレクトリへ継承してから、今回の closure で上書きするようにしています。これにより新世代は「前世代の全 namespace + 今回の更新分」の完全な集合になり、current の一発切替(atomicity)を維持したまま他 entry の stable symlink も生き続けます。current の symlink target は .hawk-generations/gen.* パターンかを検証してから読む形にし、不正な値は拒否します。テスト multi_entry_namespace_coexist(apps/a/main.awk desugar 後に apps/b/main.awk を desugar しても両方の dist/apps/*/main.awk が生存し実行できること、hardlink の同一性も確認)を追加しています。
current の全体切替で既存 entry を欠落させないため前世代を hardlink 継承し、共通の親検査で stable/compat symlink 作成時の dist 外書込みも防ぐ。
check・emit・serve の共通経路に Bash 4 要件を波及させないため、index の訪問済み集合を連想配列から既存の文字列照合へ戻す。
|
実装レビュー中に見つけた追加修正(自己指摘): |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae84f92bdc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| V2_SRC = (ARGC > 1) ? ARGV[1] : "/dev/stdin" | ||
| V2_ERRORS = 0 | ||
| v2_lex(V2_SRC) | ||
| v2_merge_shared_pre_rpn() |
There was a problem hiding this comment.
Seed lexing before shared-call checks
When an included file provides a typed function and the entry calls it from an otherwise raw AWK function, this merge point is too late: v2_lex has already built the function-call detector without the shared signatures, so the call line stays RAWLINE and skips v2_check_calls. For example, with lib.awk defining function normalize(x: Str) -> Str and main.awk containing function caller() { return normalize(1) }, hawk-libs desugar exits 0, whereas the same declarations in one file correctly report the Str/Int mismatch.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
対応しました。v2_merge_shared_pre_rpn() の呼び出しを v2_lex() の前に移し、マージ対象を V2_SHARED_SIG / V2_SHARED_ALIAS / V2_SHARED_RAW_FUNC にも拡張しています(従来は record 型/フィールドのみで、しかも lex の後というタイミングも誤っていました)。テスト shared_signature_checks_raw_caller(include 先の normalize(x: Str) -> Str を raw AWK の caller が normalize(123) で呼ぶ型不一致が検出されること)を追加しています。
| elif [[ "$parent" != "$dist" ]]; then | ||
| echo "[hawk-libs] desugar failed: output directory escapes dist: $parent" >&2 | ||
| exit 1 |
There was a problem hiding this comment.
Normalize dist before parent comparisons
If HAWK_DIST ends with a slash, valid parents are rejected here because dirname "$dist/$_rel" returns dist while $dist is dist/, so the equality check falls into the escape error path. This makes common invocations like HAWK_DIST=dist/ hawk-libs desugar main.awk fail after creating a partial .hawk-generations directory even though the output is still inside the requested dist.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
対応しました。dist 代入直後に末尾スラッシュを除去する正規化を追加しています。テスト hawk_dist_trailing_slash_normalized(HAWK_DIST=.../dist/ でも desugar が成功し、成果物が正しい場所に置かれること)を追加しています。
include 先の関数シグネチャを raw AWK 呼び出しでも検査し、末尾スラッシュ付き HAWK_DIST を正常に扱うため。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 75d1169034
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| case "$entry_key" in ../*|*/../*|*" "*) | ||
| echo "[hawk-libs] desugar failed: invalid entry path: $entry_key" >&2 | ||
| exit 1 | ||
| ;; |
There was a problem hiding this comment.
Reject reserved entry namespaces before staging
When the entry itself is under a reserved namespace, e.g. running hawk-libs desugar .hawk-dist/main.awk from the project root, this validation lets .hawk-dist/main.awk become the namespace. The publish path then creates dist/.hawk-dist as a directory for the stable link, and the later marker copy fails after dist/current has already been switched, leaving a partial generation active and the marker path corrupted. Reject reserved entry namespaces such as .hawk-dist, .hawk-generations, and current here before any staging is created.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
対応しました。entry_key 正規化直後に .hawk-dist/.hawk-generations/current を先頭 namespace とする entry を staging 前に拒否しています。テスト reserved_entry_namespace_rejected を追加しています。
| if [[ ! -e "$_stable" && ! -L "$_stable" ]]; then | ||
| ln -s "$runtime_ns/$_rel" "$_stable" | ||
| fi | ||
| if [[ "$_compat" != "$_stable" && ! -e "$_compat" && ! -L "$_compat" ]]; then | ||
| ln -s "$runtime_ns/$_rel" "$_compat" |
There was a problem hiding this comment.
Refresh existing stable links that point away
If a previously published stable or compat symlink is replaced or becomes stale, this block treats any existing symlink as acceptable and never recreates it. For example, after one successful publish, replacing dist/main.awk with a symlink to an outside file and rerunning exits 0 and still returns dist/main.awk, but that path executes the outside file instead of the new dist/current generation. Existing symlinks should be validated against $runtime_ns/$_rel or atomically recreated.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
対応しました。stable/compat symlink の既存 target を readlink で確認し、$runtime_ns/$_rel と異なる場合のみ mktemp + アトミック置換で更新するようにしています(一致していれば無変更)。BSD mv -h / GNU mv -T の feature-detect ロジックは current symlink 切替と共通化しています。テスト stale_stable_symlink_refreshed を追加しています。
| for (k in ALIAS) { | ||
| if ((k in V2_INDEX_ALIAS) && V2_INDEX_ALIAS[k] != ALIAS[k]) { | ||
| print "[hawk-libs] index failed: conflicting type alias definition: " k > "/dev/stderr" | ||
| exit 1 | ||
| } | ||
| V2_INDEX_ALIAS[k] = ALIAS[k] |
There was a problem hiding this comment.
Reject alias/record name collisions
When one file in the include closure defines type User = { ... } and another defines type User = Str, the index accepts both because aliases are only compared against prior aliases, not record names. The generated shared environment then contains both V2_SHARED_RECORD_TYPE["User"] and V2_SHARED_ALIAS["User"], so record literals in the record file can be lowered as the scalar alias and the published AWK fails at runtime (e.g. attempt to use scalar parameter ... as an array) instead of failing closed during indexing.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
対応しました。record 登録時に同名が既存 alias にあれば拒否、alias 登録時に同名が既存/現在の record にあれば拒否と、両方向の衝突を index 時点で fail closed にしています。テスト alias_record_name_collision_rejected(include 順を正逆とも検証)を追加しています。
予約 namespace・改ざん済み stable link・record/alias 型名衝突で壊れた成果物を公開しないため。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a9e1612a7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for ((i = 0; i < ${#_pub_rel[@]}; i++)); do | ||
| _rel="${_pub_rel[$i]}" | ||
| _prepare_dist_parent "$(dirname "$dist/${namespace:+$namespace/}$_rel")" | ||
| _prepare_dist_parent "$(dirname "$dist/$_rel")" |
There was a problem hiding this comment.
Reject reserved compat parents before mkdir
Fresh evidence: the include path current/x.awk, not an entry path, still reaches this compat-parent mkdir. When a project-relative include uses the reserved current/ prefix, this line treats $dist/current as a normal compat directory and creates it before the current symlink is installed; the subsequent atomic replace fails because current is now a directory, leaving a partial .hawk-generations tree and blocking later runs. Reject reserved dist namespaces for included rels before preparing compat parents.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
対応しました。current/current/*/.hawk-generations/.hawk-generations/* を include 側の rel(.hawk-dist 予約名と同じ case 文)でも予約 namespace として拒否するようにしています。テスト reserved_namespace_include_rejected(current/x.awk を include するケース)を追加しています。
公開先の current と generation 管理領域を include が占有すると atomic 切替が壊れるため、desugar 前に拒否する。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5ba0cf7569
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| echo "[hawk-libs] desugar failed: output directory escapes dist via symlink: $compat_parent_phys" >&2 | ||
| exit 1 | ||
| fi | ||
| if [[ -d "$stable" || ( -e "$stable" && ! -L "$stable" ) || -d "$compat" || ( -e "$compat" && ! -L "$compat" ) ]]; then |
There was a problem hiding this comment.
Refresh directory symlinks before rejecting outputs
When an existing stable or compat output symlink points at a directory, -d follows the symlink and this check exits before _refresh_output_link can atomically replace it. For example, after a successful publish, replacing dist/main.awk with a symlink to any directory makes the next hawk-libs desugar main.awk fail with output path is not replaceable instead of repairing the stale link, even though symlinks to files are now refreshed. Treat only real directories as non-replaceable here, e.g. exclude -L symlinks from the -d branch.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
対応しました。非置換判定を -d の直接チェックから (-e && ! -L) のみに統一し、symlink がディレクトリを指すケースも _refresh_output_link によるアトミック修復の対象になるようにしています(実ディレクトリは引き続き非置換として拒否されます)。テスト stale_directory_symlink_refreshed を追加しています。
framework 外の cwd でも HAWK_LIB から実行資源を解決し、置換可能な directory symlink を generation 出力へ戻せるようにする。
|
段2(wiki プロジェクト template ブランチ)の設計検証中に見つけた別件の修正(コミット 既存のすべての Hawk デプロイは cwd と 修正は、 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f89404966
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (( i=0; i<EFFECTIVE_WORKERS; i++ )); do | ||
| # shellcheck disable=SC2086 | ||
| LC_ALL=C gawk -b $LIBS_ARGS $LIBS_VARS -f hawk.awk "${_plugin_args[@]}" -f "$APP_AWK" & | ||
| ( cd "$HAWK_LIB" && LC_ALL=C gawk -b $LIBS_ARGS $LIBS_VARS -f "$HAWK_LIB/hawk.awk" "${_plugin_args[@]}" -f "$APP_AWK" ) & |
There was a problem hiding this comment.
Keep app workers in the app cwd
This single-worker launch path (and the analogous cd "$HAWK_LIB" in hawk-worker for supervised mode) changes the process cwd before user code runs, so relative paths in the app now resolve under the framework checkout instead of the project directory where hawk serve was invoked. Apps using patterns like read_tsv("data/todos.tsv", ...) or unrooted template paths will silently miss their project files once HAWK_LIB differs from the app cwd; keep gawk running in the app cwd and resolve framework includes via absolute -f/AWKPATH instead.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
指摘のとおりです。cd "$HAWK_LIB" はアプリ自身の相対パス(read_tsv 等)も巻き込んで壊すため、cwd を変えないアプローチに直します。修正中です。
There was a problem hiding this comment.
修正しました(3100de3)。cd "$HAWK_LIB" は撤去し、代わりに hawk-libs runtime-awk サブコマンドを新設して、hawk.awk 自身の framework-local @include チェーンだけを起動前に絶対パスで再帰展開した一時ファイル(hawk-serve は mktemp、hawk-worker は process substitution)を -f で渡す方式にしています。gawk プロセスの cwd 自体はプロジェクトルートのまま変更しないため、アプリの相対パス読み込み(read_tsv("data/items.tsv", ...) 等)は影響を受けません。実機検証(cwd=プロジェクトルート、HAWK_LIB=別ディレクトリで read_tsv の相対パス読み込みが正常動作)と回帰テストを追加しています。
なお、この修正が hawk-libs runtime-awk の生成コストを起動のたびに発生させる副作用があり、それに伴う E2E テストのタイミング問題と、supervised worker 再起動時の再生成コストの2点も追加で見つかり、e3edbfe(E2E をポーリング方式に変更、supervised worker はキャッシュされた bundle を再利用)で対処済みです。
framework の include だけを起動前に再帰展開し、worker がプロジェクト相対のデータを従来どおり読めるようにする。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e3edbfee8d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| _stable="$dist/${namespace:+$namespace/}$_rel" | ||
| _compat="$dist/$_rel" | ||
| _refresh_output_link "$_stable" "$runtime_ns/$_rel" | ||
| [[ "$_compat" == "$_stable" ]] || _refresh_output_link "$_compat" "$runtime_ns/$_rel" |
There was a problem hiding this comment.
Reject unowned compat symlinks before refreshing
When publishing a namespaced entry, the compat path ($dist/$_rel) can already be a user-owned symlink even though the marker has no matching src_root/rel entry. The earlier ownership check only validates _stable, so a first run like HAWK_DIST=dist hawk-libs desugar apps/a/main.awk will replace an existing dist/main.awk -> /some/user/file symlink with the generated compat link instead of refusing to touch an untracked output; the namespace/stable path is protected, but this refresh path bypasses the same marker guard for compat symlinks.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
対応しました。compat path の上書き可否を、marker に記録された rel の所有者(src_root)で3通りに分岐するようにしています: 自身が所有していれば通常どおり refresh、別の source が所有していれば(複数 entry 共存を壊さないよう)refresh をスキップして既存を保持、どこにも記録がなければ publish 前に拒否します。テスト unowned_compat_symlink_rejected と、既存の cross_source_same_basename_isolated への検証追加(apps/b の desugar 後も apps/a の compat symlink が無変更であること)を追加しています。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aca3671565
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| exit 1 | ||
| fi | ||
| if [[ -e "$compat" || -L "$compat" ]]; then | ||
| if [[ ! -f "$dist/.hawk-dist" ]] || ! grep -qxF -- "$src_root"$'\t'"$rel" "$dist/.hawk-dist"; then |
There was a problem hiding this comment.
Preserve compatibility symlink owner across reruns
When one dist is shared by entries with the same rel path, the first apps/b/main.awk publish skips dist/main.awk because apps/a already owns that compatibility path, but it still appends the apps/b marker entry. On a later rerun of apps/b/main.awk, this exact-pair check succeeds and bypasses the rel-collision scan, so dist/main.awk is refreshed from the apps/a target to apps/b; users still invoking the root compatibility path get the wrong app. Keep skipping when any other source already owns the same compat rel, or record the actual compat owner separately.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in a29364d.
.hawk-dist マーカーを2列(src_root\trel、stable 所有権)から3列(+compat タグ、compat 所有権を別トラック)に拡張しました。compat symlink の refresh を別 source の所有によりスキップしたケースでは、compat 所有権行を一切書き込まなくなったため、次回同じ source が実行しても「他 source が所有している」と正しく再判定され、他 source の compat symlink を上書きしなくなります。
回帰テスト cross_source_compat_owner_persists_on_rerun を追加:apps/b が初回 skip → 2回目 rerun でも apps/a の compat symlink が不変であることを検証しています。
make ci 全項目通過(unit 516 / DSL 183 / DSL2 325 / CLI 15 / desugar 81 / E2E 12、失敗0)。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a29364dc6f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (k in V2_INDEX_RECORD_FIELDS) V2_INDEX_OLD_FIELDS[k] = V2_INDEX_RECORD_FIELDS[k] | ||
| delete V2_INDEX_OLD_RECORDS | ||
| for (k in V2_INDEX_RECORD_NAMES) V2_INDEX_OLD_RECORDS[k] = 1 | ||
| v2_lex(files[i]) |
There was a problem hiding this comment.
Reset lexer state between indexed files
This loop indexes every file in a single gawk process, but v2_lex() does not clear per-file line state such as V2_FORCE_DSL before reading the next file. When an earlier file has an unannotated DSL function covering lines 1-4, those line numbers remain forced for a later include that is otherwise raw AWK; the index can then emit a typed SIG for that raw function, causing a sibling raw caller like raw(1, 2) to fail desugar with an arity error instead of staying raw as before. Clear the per-file lexer arrays before each v2_lex() call or inside v2_lex().
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 780de69.
v2_lex() の先頭で TOK / PASS / V2_RAWLINE / V2_LINE_TEXT / V2_FORCE_DSL を delete してから走らせるようにしました。以前は V2_FORCE_DSL がクリアされず、あるファイルで force-mark された行番号が後続ファイルの同じ行番号帯に漏れて誤って DSL 扱いされる状態でした。
回帰テスト index_lex_state_isolated_per_file を追加:1〜4行目にDSL構文を含む未注釈関数を持つファイルAの後に、同じ行番号帯にraw AWK関数raw(a, b)を持つファイルBをindexし、rawがV2_SHARED_SIGに誤登録されないこと、および実際にcaller() { return raw(1, 2) }のdesugarが成功することを検証しています。
| case "$subcmd" in | ||
| plugins) | ||
| for d in plugins/*/; do | ||
| for d in "$HAWK_LIB"/plugins/*/; do |
There was a problem hiding this comment.
Keep plugin loading and discovery on the same root
When HAWK_LIB points at a framework checkout outside the app, this now loads plugin files from $HAWK_LIB/plugins, but runtime registration still scans plugins relative to the app cwd in core/plugin.awk (plugin_discover). In that context framework plugins are loaded with -f but never registered unless the app also has a matching plugins/<name> directory, while app-local plugin directories can be discovered without their manifest having been loaded. Use a single plugin root for both the loader and plugin_discover.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 780de69.
plugin_discover() の列挙元(ls plugins)と .disabled 判定を、loader 側(hawk-libs plugins サブコマンド)と同じ ENVIRON["HAWK_LIB"]/plugins(未設定時は ./plugins にフォールバック)に統一しました。パスは既存の _shellquote(core/util.awk)でシェルクォートしています。
回帰テスト test_plugin_discover_hawk_lib_root を追加:HAWK_LIB が app cwd と異なるディレクトリを指す状態で、framework 側の plugin が正しく登録されること、app cwd 側にだけ存在する無関係な plugin ディレクトリ(.disabled 付き同名含む)が登録・影響を与えないことを検証しています。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 780de69db0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| function plugin_discover( cmd, pname, func_name, meta) { | ||
| cmd = "ls plugins 2>/dev/null" | ||
| function plugin_discover( cmd, pname, func_name, meta, root, disabled) { | ||
| root = (ENVIRON["HAWK_LIB"] != "" ? ENVIRON["HAWK_LIB"] : ".") "/plugins" |
There was a problem hiding this comment.
Export HAWK_LIB before relying on ENVIRON
Fresh evidence: this now reads only ENVIRON["HAWK_LIB"], but hawk-serve, hawk-worker, and hawk-supervise default HAWK_LIB as an unexported shell variable when invoked directly. In that context hawk-libs plugins still loads manifests from the framework root because it computes its own default, while plugin_discover() falls back to ./plugins; framework plugin hooks are never registered unless the user came through bin/hawk or manually exported HAWK_LIB. Export the wrapper default or pass the root into gawk so loading and discovery stay on the same root.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b724586.
指摘のとおりでした。libexec/hawk-serve / hawk-worker / hawk-supervise / hawk-check / hawk-emit の5エントリポイントいずれも HAWK_LIB="${HAWK_LIB:-...}" が非exportな代入で、bin/hawk 経由(libexec/hawk がexport済み)以外の直接起動では子gawkプロセスのENVIRONに伝わっていませんでした。5ファイルとも export HAWK_LIB=... に統一しています(HAWK_LIBEXEC は今回の指摘対象外のため未変更)。
回帰テスト direct_entrypoints_export_hawk_lib を追加:HAWK_LIB を事前unsetした状態で hawk-worker を直接起動し、子gawk側の ENVIRON["HAWK_LIB"] が正しく見えること、および5エントリポイント全てで export HAWK_LIB= になっていることを検証しています。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b724586df6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| [[ "$_existing" == "$dist/.hawk-dist" || "$_existing" == "$dist/.hawk-generations/"* ]] || { _dist_has_source=1; break; } | ||
| done < <(find "$dist" -type f -print) |
There was a problem hiding this comment.
Allow marker-owned dist files during migration
When upgrading a project that already has the old default dist/ layout, marker-owned outputs such as dist/main.awk are regular files under src_root. This scan counts every regular file except the marker/generation internals as source, so the first hawk-libs desugar main.awk after the generation-dist change aborts with dist and source trees overlap before marker ownership can prove the files are generated and safe to migrate. Exclude files recorded in .hawk-dist (and migrate them to the new symlink layout) instead of treating them as source.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 006fe8d.
.hawk-dist マーカーは本PR以前(#119着手前)から存在し、旧flat publish(symlinkでなくdist配下へregular fileを直接書き出す方式)でも自己所有を2列で記録していました。つまり既存の全hawkプロジェクトのdistには、marker所有の旧レイアウトregular fileが既にある状態で、本PRの generation-dist 導入後の初回再実行で自分自身の過去の成果物を「sourceと衝突している」と誤検知して詰まる状態でした。
marker完全一致判定を _marker_owns_output() に共通化し、3箇所で自己所有($src_root + rel、stable=2列/compat=3列)のregular fileのみを「migration対象」として許可するようにしています:
- nested dist の事前source scan
- stable/compat の「置換不可」チェック(kind別に分離)
_refresh_output_link()(既存のatomic symlink置換ロジックで、自己所有regular fileをsymlinkへ移行)
自己所有以外(無関係な素のuser source等)は従来通り拒否したままです。回帰テスト legacy_stable_regular_migrated / legacy_compat_regular_migrated を追加し、既存の dist_over_source_rejected(marker無しの真のsourceは引き続き拒否される)が保護として機能し続けることも確認しています。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 006fe8d5e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if [[ -e "$stable" && ! -L "$stable" ]] && ! _marker_owns_output "$src_root" "$rel"; then | ||
| echo "[hawk-libs] desugar failed: output path is not replaceable: $stable" >&2 | ||
| exit 1 | ||
| fi | ||
| if [[ "$compat" != "$stable" && -e "$compat" && ! -L "$compat" ]] && ! _marker_owns_output "$src_root" "$rel" compat; then |
There was a problem hiding this comment.
Reject marker-owned directories before publishing
When a previously published output is replaced by a real directory, the marker ownership check here treats it as replaceable and allows the publish to proceed; _refresh_output_link only fails later when mv cannot replace the directory, after dist/current has already been switched. This leaves the new generation active while the stable/compat path is still a directory; reject -d && ! -L outputs before the current symlink is updated, while still allowing marker-owned regular files to migrate.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 3260257.
stable/compat の置換前チェックと _refresh_output_link() の3箇所すべてに、実ディレクトリ(-d && ! -L)を marker 所有の有無に関わらず常に拒否する分岐を、marker 自己所有による regular file migration 許可より前に追加しました。これにより dist/current の世代切替が起きる前の preflight で確実に exit 1 します(従来は _refresh_output_link 内の mv 失敗まで先送りされ、新世代が active のまま stable/compat パスが directory で壊れる中途半端な状態が残っていました)。symlink がディレクトリを指すケースは ! -L で除外されるため、既存の stale_directory_symlink_refreshed(アトミック修復)の動作は維持されます。
回帰テスト marker_owned_directory_rejected_early を追加:自己所有 marker 付きの stable パスに実ディレクトリを置いた状態で desugar が exit 1 し、ディレクトリが無変更で残り、dist/current が一切作られない(=世代切替前に拒否)ことを検証しています。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 326025752c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| echo "[hawk-libs] desugar failed: output path is not replaceable: $compat" >&2 | ||
| exit 1 | ||
| fi | ||
| if [[ -e "$compat" || -L "$compat" ]]; then |
There was a problem hiding this comment.
Preserve compat ownership when the link is missing
Fresh evidence: this ownership scan only runs when the compatibility path currently exists. If dist/main.awk is deleted after apps/a owns it in .hawk-dist, rerunning apps/b/main.awk skips _compat_skip, appends an apps/b compat marker, and recreates dist/main.awk pointing at apps/b; the compatibility entry silently changes owners despite the marker still recording apps/a. Read the marker before the existence check, or treat existing compat ownership as a skip even when the symlink is absent.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7a9516f.
marker の所有権スキャン(compat_owned / compat_other / stable_owned / stable_other の集計と _compat_skip への積み込み)を compat path の存在チェックの外へ移し、常に実行するようにしました。「無記録の既存 compat を拒否する」分岐だけは従来通り実在時(-e || -L)に限定しています。
これにより link 実在時の全ケースは従来と同一挙動のまま、link 欠落時は:
- 別 source が compat を所有 → skip(link 再作成も compat marker 追記もしない。link は所有者の次回 rerun で修復される)
- 自身が単独所有 → 通常 refresh で自己修復
- 無記録 → 通常の初回 publish
回帰テストを2件追加:missing_compat_other_source_skips(apps/a 所有の compat link を削除後に apps/b を実行しても、link が apps/b 向きに再作成されず apps/b の compat marker 行も追記されないこと)、missing_compat_owner_repairs(所有者 apps/a 自身の再実行では欠落 link が元の target で再作成されること)。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a9516f17d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| _scratch_arr+=("$_marker_tmp") | ||
| { | ||
| [[ -f "$dist/.hawk-dist" ]] && cat "$dist/.hawk-dist" | ||
| for _rel in "${_pub_rel[@]}"; do printf '%s\t%s\n' "$src_root" "$_rel"; done |
There was a problem hiding this comment.
Key stable markers by published namespace
When the same source is desugared into a shared absolute HAWK_DIST from different working directories, its namespace changes (for example repo root publishes dist/apps/a/main.awk, while running from apps/ publishes dist/a/main.awk), but this marker records only src_root<TAB>main.awk. Later ownership checks treat that old marker as permission to replace the new stable path, so an unrelated existing dist/a/main.awk symlink or regular file is accepted and _refresh_output_link overwrites it; record/validate the namespaced stable rel instead of the bare rel.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in dcadd8b.
stable の marker キーを bare rel から publish 先の dist 相対実パス(${namespace:+$namespace/}$rel)に変更しました。marker 書き込み、preflight の置換可否チェック、既存 stable 上書きチェック、staging への hardlink 継承ファイルの上書きチェック、_refresh_output_link の所有判定、すべて同じ namespaced キーで統一しています。
compat 行は実パスが dist/<rel> で namespace 非依存のため bare rel + compat タグのまま、また compat 所有権スキャンの stable フラグ(旧 flat layout の2列 bare rel 行への後方互換フォールバック)も bare 比較のまま維持しています(旧 layout は namespace 概念が無いため bare のみで一致するのが正しく、新形式では明示的な compat 行が所有権を完全に決定します)。
回帰テストを2件追加:cross_cwd_namespace_marker_isolated(repo root から publish(namespace apps/a)後、無関係な symlink を dist/a/main.awk に置いた状態で apps/ cwd から同一 source を desugar(namespace a)すると exit 1 で拒否され symlink が無変更で残ること)、namespaced_same_cwd_rerun(同一 cwd からの rerun は引き続き成功すること)。legacy migration 系(empty namespace で stable_rel == bare rel)も無変更で通っています。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dcadd8b012
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| case "$subcmd" in | ||
| plugins) | ||
| for d in plugins/*/; do | ||
| for d in "$HAWK_LIB"/plugins/*/; do |
There was a problem hiding this comment.
Load app plugins from the documented project root
Fresh evidence: docs/plugins.md line 5 still documents project-local plugins/<name>/ as the startup plugin location, but when HAWK_LIB points to an installed framework and the app is run from a separate cwd, this loader now scans only $HAWK_LIB/plugins. In that installed-framework setup, an app's ./plugins/logger/{manifest.awk,logger.awk} is never passed to gawk (and the matching .disabled file is ignored), so documented app plugins silently stop loading; keep the app plugin root on the project cwd or provide a separate framework/plugin root without replacing the app one.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 41314c0.
plugin root を $HAWK_LIB/plugins(framework 側)と ./plugins(app cwd 側、docs 記載の従来仕様)の両立に変更しました。root の列挙・shadowing・dedup ロジックは hawk-libs 内の _plugin_dirs() に一元化し、plugins(-f 用ファイル列挙)と新設 plugin-dirs(選定済み plugin ディレクトリ列挙)で共有しています。仕様:
- 同名 plugin は app 側が優先(framework 側は列挙されない)
- app 側の
.disabled付き同名ディレクトリは framework 側も shadow(ローカルで明示無効化した意図を尊重) - framework root と app root が物理的に同一(従来の cwd == HAWK_LIB 構成)なら単一スキャンで重複ロードなし
hawk-serve / hawk-supervise が plugin-dirs の結果を HAWK_PLUGIN_DIRS として export し、plugin_discover() はそれを消費するだけ(未設定時は従来の HAWK_LIB fallback を維持)にしたので、loader と discovery の root が構造的に一致します。awk 側での物理パス比較(BSD/GNU stat 差の温床)は行っていません。docs/plugins.md と ja 版に両 root 仕様を追記しました。
テスト5件追加(plugins_dual_roots / plugins_app_shadow / plugins_disabled_app_shadows / plugins_same_root_dedup / plugin_dirs_match_selection)、test_plugin_discover_hawk_lib_root は新契約(env 経由で両 root 登録、fallback は HAWK_LIB のみ)に更新しています。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 41314c024e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| echo "[hawk-libs] desugar failed: output path is not replaceable: $compat" >&2 | ||
| exit 1 | ||
| fi | ||
| if [[ "$compat" != "$stable" && -e "$compat" && ! -L "$compat" ]] && ! _marker_owns_output "$src_root" "$rel" compat; then |
There was a problem hiding this comment.
Accept old flat markers at compat paths
Fresh evidence: namespaced old-flat publishes still record the generated dist/main.awk with the legacy two-column marker (src_root<TAB>main.awk), but after this change that file is the compatibility path, so this check only accepts a new three-column compat marker and aborts before migration. Upgrading a project that previously ran hawk-libs desugar apps/a/main.awk therefore fails with output path is not replaceable: dist/main.awk instead of converting the marker-owned regular file to the new stable/compat symlinks.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in dbcc13e.
_marker_owns_output() の kind=compat 分岐を「正規の3列 compat 行に一致」または「legacy 2列行(src_root<TAB>rel、bare)に一致」の OR に変更しました。legacy 2列行は「その src_root が旧 flat layout で dist/<rel> を publish した」所有記録であり、compat path は dist/<rel> そのものなので、同一 src_root・同一パスの所有証明として妥当です。新形式の namespaced stable 行(src<TAB>ns/rel)は bare grep に一致せず、grep は src_root 前提の行全体完全一致なので他 src_root への誤許可もありません。所有権スキャン本体(stable_owned fall-through が legacy を処理する経路)は変更していません。
migration 成功後の publish で正規の3列 compat 行が marker に追記されるため、以後は新形式で判定されます。
回帰テスト legacy_flat_two_column_compat_migrated を追加:実在する旧形式(2列のみの marker + flat regular file)の dist に対し、namespaced entry(apps/a/main.awk)の upgrade 実行が成功し、compat/stable とも symlink 化され内容が更新され、3列 compat 行が自動追記されることを検証しています。
Summary
#119 で先送りにしていた3課題への対応。PR #118(multi-file desugar + router catch-all)を base にした続きの PR。
dsl/index.awkを新設。include closure 全ファイルを lexer/rpn/parser の prepass に通し、record 型・type alias・関数シグネチャ・raw function を共有環境として直列化。各ファイルの desugar 実行時に共有環境を先読みすることで、sibling/entry 間の前方参照を解決する。重複定義(同名 record が異なるファイルで異なる定義)は index の時点で fail closedapps/a/main.awkとapps/b/main.awk)をdist/apps/a/main.awk/dist/apps/b/main.awkに分離。project 外の絶対パス entry は canonical path の hash namespacedist/.hawk-generations/gen.XXXXXX/への staging +dist/currentsymlink の一発切替に変更。ファイル単位 mv の連続で生じていた新旧世代混在の窓を除去。stable/compatsymlink 層でdist/<namespace>/<rel>からの参照も維持Portability
mv -h(BSD 専用)は使わず、実行時に成功/失敗を検証してからmv -T(GNU)へフォールバックする feature-detect 方式。Test plan
bash tests/unit/desugar/run.sh: 70 passed, 0 failedbash tests/unit/cli/run.sh: 11 passed, 0 failedmake ci: unit 581, dsl 183, dsl2 325, cli 11, desugar 70, e2e 12(全て 0 failed、main tree の zig-out 込みで再検証済み)Notes
🤖 Generated with Claude Code