Skip to content

adr 0004 2#4

Open
lzrscg wants to merge 176 commits intomainfrom
adr-0004-2
Open

adr 0004 2#4
lzrscg wants to merge 176 commits intomainfrom
adr-0004-2

Conversation

@lzrscg
Copy link
Copy Markdown
Contributor

@lzrscg lzrscg commented May 5, 2026

  • chore: update review-adr workflow to renamed ADR-0004 filename
  • chore: cleanup
  • docs(adr-0004): tighten cleanup safety summary, spawn-failure cleanup, lazy env-snapshot errors
  • chore: upgrade workflow
  • docs(adr-0004): add project-root cwd and LOOPX_WORKFLOW_DIR
  • docs(adr-0004): fix leak wording, absolute invocation path, §5.1 retarget
  • docs(adr-0004): weaken JS/TS import.meta.url symlink equality, clarify path spelling and warning cardinality
  • docs(adr-0004): resolve relative RunOptions.cwd, broaden symlink caveats to entry scripts
  • docs(adr-0004): fix env rationale, snapshot inherited env, broaden symlink scope, tighten warning cardinality
  • docs(adr-0004): clarify cwd spelling, PWD handling, partial tmpdir cleanup
  • docs(adr-0004): resolve env/cleanup contradictions from review feedback
  • docs(adr-0004): address remaining blockers from ADR-0001 review
  • docs(adr-0004): address symlink, cleanup taxonomy, and error-path gaps from review
  • docs(adr-0004): soften env name-validation claims and clarify cleanup timing
  • docs(adr-0004): address review blockers on warning tokens, options ordering, and cleanup caveats
  • docs(adr-0004): address remaining review blockers on abort precedence, pre-iteration error scoping, and CLI grammar
  • docs(adr-0004): tighten pre-first-next carve-out, abort-after-final-yield, run -h, warning scoping, and invalid options
  • docs(adr-0004): weaken Bash PWD claims, align version-check wording with SPEC, broaden env-tier rejection, clarify LOOPX_DELEGATED and hard-link semantics
  • docs(adr-0004): fix abort-precedence contradiction, cleanup-token scoping, recursive-removal test, version-check-under-n0 example, and make cwd/envFile validation explicit
  • docs(adr-0004): add scope-of-contract note, programmatic precedence summary, and LOOPX_DELEGATED role distinction
  • docs(adr-0004): address review feedback on timing, signal-wins scope, -- grammar, §3.2, and cleanup-token escaping
  • docs(adr-0004): fix token-escape contradiction, split runPromise env-snapshot timing, tighten impl-defined scope and CLI grammar terminology
  • docs(adr-0004): address review blockers — signal/parser race, token scoping, tmpdir parent, option-first priority, duck-typed signal reentrancy
  • docs(adr-0004): address pre-acceptance review feedback
  • docs(adr-0004): tighten to minimal-but-thorough — drop over-specification
  • docs(adr-0004): address pre-acceptance review — cancellation scope, race precedence, tmpdir hardening, §7.4
  • chore: update loopx workflows
  • docs(adr-0004): add §6 auto-install workflow npm dependencies during loopx install
  • chore: update workflows
  • docs(adr-0004): add .gitignore safeguard to §6 auto-install
  • chore: update loopx workflows
  • docs(adr-0004): tighten pre-acceptance review — .gitignore failure, §10.7/§10.9/§11.3, scope
  • chore: update loopx workflows
  • docs(adr-0004): update SPEC.md for script execution context, CLI grammar, and auto-install
  • docs(adr-0004): apply post-acceptance feedback to SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • chore: update loopx workflows
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • chore(review-test-spec): scope review and SPEC-PROBLEMS to active ADR
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): resolve P-0004-01 / P-0004-02 and apply review feedback
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): resolve P-0004-03 and apply review feedback
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): resolve P-0004-04 and apply review feedback
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): resolve P-0004-07 and apply review feedback
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): resolve P-0004-08 (broken/cyclic symlinks during runtime discovery)
  • docs(adr-0004): open P-0004-09 (project-root .loopx symlink failure modes)
  • docs(adr-0004): resolve P-0004-09 (project-root .loopx failure modes)
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): clarify T-API-70 to avoid implying a fixed pre-iteration priority
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): resolve P-0004-10 and P-0004-11
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): resolve P-0004-12 and apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): open P-0004-13 and apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): resolve P-0004-13 and apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): apply post-acceptance feedback to TEST-SPEC.md
  • docs(adr-0004): advance status to Test Specified
  • chore: update loopx workflows

lzrscg and others added 30 commits April 20, 2026 10:49
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, lazy env-snapshot errors

Address review feedback by:
- Rewriting the Decision Summary cleanup bullet to match the detailed
  rules (symlink replacements are unlinked; only identity-matched
  directories are recursively removed).
- Adding "child launch / spawn failure after tmpdir creation" as an
  explicit terminal outcome so the tmpdir guarantee covers cases like a
  discovered script being removed between discovery and spawn or an OS
  rejection of a RunOptions.env entry.
- Clarifying that the shell env-var prefix on the CLI reuses the
  inherited-env tier and does not introduce a new override-precedence
  level above -e or global env.
- Preserving SPEC §9.1's sync-never-throws guarantee when
  RunOptions.env is an exotic object (Proxy, throwing getter) by
  routing snapshot exceptions through the pre-iteration error path.
- Spelling out the five-tier env precedence list in the §8 SPEC-update
  mapping and extending §7.2 to cover spawn-path failures.

Status remains Proposed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expands ADR-0004 with two new decisions that make scripts easier to
write:

- §3 Project-root cwd. Scripts run with LOOPX_PROJECT_ROOT as their
  child-process cwd instead of the workflow directory, superseding
  SPEC §6.1's current "workflow directory as cwd" rule. Eliminates
  the \$ROOT="\$LOOPX_PROJECT_ROOT" prefix boilerplate currently
  repeated in every workflow script.
- §4 LOOPX_WORKFLOW_DIR injection. Absolute path to the
  currently-spawned script's workflow directory, refreshing per-spawn
  alongside LOOPX_WORKFLOW. Correct across intra-workflow goto,
  cross-workflow goto (destination sees its own dir, not caller's),
  deep chains, and loop reset. Gives scripts a one-token reference
  to workflow-local assets without dirname/import.meta.url tricks.

Also corrects SPEC §3.3's stale claim that closer-node_modules/loopx
precedence is "a natural consequence of running scripts with the
workflow directory as cwd" — module resolution is file-relative, not
cwd-relative.

Renamed from 0004-tmpdir-and-env.md → 0004-script-execution-context.md
to reflect the broader scope (now four decisions: LOOPX_TMPDIR,
RunOptions.env, project-root cwd, LOOPX_WORKFLOW_DIR).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rget

Apply review feedback: weaken the async-generator cleanup claim (no
GC-based guarantee), make the absolute discovery-time invocation path
a normative rule so $0 / import.meta.url equal LOOPX_WORKFLOW_DIR,
list §5.1 in Affected SPEC Sections so its "non-zero exit" reference
aligns with §7.2's new spawn-failure category, note that
LOOPX_PROJECT_ROOT is symlink-preserving like LOOPX_WORKFLOW_DIR,
drop src/env.ts and src/execution.ts implementation refs, and correct
the "loopx env set" consequence to reflect its persistent-global role.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y path spelling and warning cardinality

Address review feedback pre-acceptance:

- JS/TS dirname(fileURLToPath(import.meta.url)) equality with LOOPX_WORKFLOW_DIR
  is now non-normative under symlinks (Node canonicalizes the main module's URL
  by default; Bun's behavior is unspecified). Bash $(dirname \"\$0\") equality
  remains normative. LOOPX_WORKFLOW_DIR is authoritative for JS/TS code.
- Replace \"path-spelling preserved\" prose with \"no extra canonicalization by
  loopx\" for LOOPX_PROJECT_ROOT, explicitly acknowledging that process.cwd()
  may canonicalize while RunOptions.cwd is used verbatim.
- Normalize cleanup warning cardinality to one stderr warning per distinct
  anomaly, no aggregation or dedup; test-friendly.
- Retarget \"§3.2 / §7.1 pre-iteration\" to §7.1 only; add §6.2 and §6.3
  entries for the language-specific invocation-path consequences.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ats to entry scripts

Closes the logical gap between "LOOPX_PROJECT_ROOT is absolute" and the
prior "RunOptions.cwd used verbatim" wording: a relative cwd is now
resolved via path.resolve(process.cwd(), options.cwd) at call time with
no further realpath. Broadens the JS/TS import.meta.url equality and
§3.3 module-resolution caveats to cover symlinked entry script files
(not only symlinked workflow directories), matching SPEC §5.1's symlink
policy on both. Adds a one-line note that OS-rejected RunOptions.env
entries don't surface under maxIterations: 0 since no spawn occurs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mlink scope, tighten warning cardinality

- Context: fix confused `.loopx/shared/` sentence (workflow-local paths
  break after cross-workflow goto because $LOOPX_WORKFLOW updates).
- §2 RunOptions.env: add "inherited process.env snapshotted once per
  run" rule so deterministic env is a per-run property, not just a
  RunOptions.env property.
- §1 warning cardinality: replace "any other distinct cleanup error
  surface" catch-all with a closed enumeration of three categories
  (leave-in-place, top-level recursive-removal failure, partial-dir
  cleanup failure on creation error).
- §3 / §4 / Affected-SPEC / Tests: consistently frame JS/TS
  import.meta.url non-guarantee around any symlink in the absolute
  discovery-time entry path (workflow dir, entry script file, or
  intermediate component) rather than only the first two. Frame the
  loopx guarantee around what loopx controls (absolute invocation
  path + LOOPX_WORKFLOW_DIR).
- Consequences: fix incorrect claim that `-e`/`loopx env set` reach
  "above env files" (`-e` is local env file tier; `loopx env set`
  writes the global env file tier). Replace RunOptions.env rationale:
  it exists as an in-memory per-call override layer that (a) avoids
  mutating process.env, (b) avoids writing a temp env file, and (c)
  layers on top of RunOptions.envFile — not because programmatic
  callers lack an -e equivalent (they have RunOptions.envFile).
- Tests: add inherited-env snapshot test, intermediate-symlink
  scenario test, and updated warning-cardinality assertions to match
  the closed enumeration.

Status remains Proposed.
…eanup

Addresses review feedback on ADR-0004:

- Distinguish effective-cwd (loopx-controlled) from cwd string spelling
  (runtime-reported); runtime APIs may canonicalize while LOOPX_PROJECT_ROOT
  preserves caller spelling.
- Document that PWD is not a protocol variable; loopx neither sets nor
  unsets it, Bash regenerates $PWD on startup.
- Specify partial tmpdir cleanup when identity capture fails: single
  non-recursive rmdir attempt, then leave-in-place + category-3 warning.
- Make RunOptions.env shape validation explicit under maxIterations: 0.
- Extend Affected SPEC Sections list with §5.1 symlink-path preservation
  and §12 tmpdir/spawn exit-code coverage; note envFile resolution under
  the new cwd semantics.
- Soften "module resolution is unaffected by cwd" to apply specifically
  to script module resolution, not cwd-dependent tooling.
- Scope byte-for-byte cwd test assertions to symlink-free fixtures; add
  filesystem-identity assertions for symlinked regimes.
- Add Rejected / Deferred Alternatives section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix symbol-key contradiction in RunOptions.env shape validation (symbol-keyed
entries are ignored, not invalid). Replace path.resolve with fs.realpath in
the symlink-identity test convention (path.resolve is string-only and does
not prove filesystem identity). Simplify warning cardinality to reflect that
cleanup dispatches on a single top-level LOOPX_TMPDIR path, so each of the
three categories fires at most once per cleanup and the three are mutually
exclusive. Document the deliberate lazy-vs-call-time asymmetry between
inherited process.env (lazy) and RunOptions.env / RunOptions.cwd (call-time),
with a corresponding test recommendation. Clarify that RunOptions.envFile
shares the local-env-file precedence tier with -e, and that invalid
RunOptions.env shape / snapshot exceptions are pre-spawn failures that do
not create a tmpdir.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes four internal inconsistencies flagged during review of ADR-0004:

- Warning cardinality: categorize by cleanup routine (full safety vs.
  non-recursive rmdir), not by terminal-vs-creation outcome. Identity-
  captured creation-failure cleanup now correctly emits cat 1/2 rather
  than claiming mutually-exclusive-with-cat-3 while sharing a routine.
- Closed-set language: switch to strict version — implementations emit
  no cleanup warnings outside the three categories; tests may assert
  their absence.
- CLI symlink spelling: drop claim that CLI invocation through a symlink
  can preserve shell-side spelling. LOOPX_PROJECT_ROOT (CLI) is exactly
  process.cwd() at invocation; loopx does not consult \$PWD. Symlinked-
  project-root asymmetry reachable only via programmatic RunOptions.cwd.
- Custom resolve hook: explicitly require the Node/tsx hook to preserve
  standard module resolution precedence via defaultResolve delegation,
  so workflow-local node_modules/loopx still wins after the cwd change.

Also clarifies RunOptions.env inspection timing (synchronous at call
time, errors deferred to pre-iteration) and notes that cleanup safety
does not detect mount points inside \$LOOPX_TMPDIR.

Status remains Proposed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s from review

Fix contradictions and close gaps flagged in ADR-0001 review feedback:

- Correct the relative-cwd symlink claim: symlinked components in a
  relative RunOptions.cwd are preserved by path.resolve(), so a
  symlink-preserving LOOPX_PROJECT_ROOT is not restricted to symlinked
  absolute cwd inputs.
- Broaden cleanup warning category 2 from "Top-level recursive-removal
  failure" to "Top-level cleanup failure" covering lstat dispatch
  failure, top-level symlink unlink failure, and identity-matched
  recursive removal failure; preserves the at-most-one-warning model.
- Make abort precedence across pre-iteration explicit: an already-
  aborted signal wins over validation, discovery, env loading, target
  resolution, version check, and tmpdir creation errors.
- Document .loopx itself as a symlink: treated as an intermediate
  component of the absolute entry path with its spelling preserved.
- Capture options.env getter exceptions through the lazy pre-iteration
  error path, like snapshot exceptions.
- Tighten module-resolution hook language: any standard file-relative
  resolution wins over the CLI fallback, not only workflow-local.
- Add SPEC §9.3 to Affected Sections; extend §7.2 bullet to cover
  tmpdir creation failure alongside other runtime error paths.
- Add test recommendations for cleanup-internal failures, relative cwd
  with symlinked components, and symlinked .loopx directory entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… timing

Blocking fix: revise RunOptions.env malformed-name behavior. Node's
child_process does not reliably reject names containing "=" (a key like
"A=B" with value "C" may reach the child as A=B=C rather than a spawn
failure). Reframe runtime-level rejection around embedded NUL bytes as
the reliable case, note that other malformed-but-runtime-accepted names
have runtime-dependent child-observed behavior, and update the matching
test recommendation.

Also clarify: run() yields any final Output before the generator
settles, with cleanup guaranteed before { done: true } / abort
surfacing; abort precedence applies only once options.signal is
captured (a throwing signal getter is an ordinary snapshot error);
summary lists consumer-driven generator cancellation as a terminal
outcome; SPEC §3.3 update language uses "any standard file-relative
resolution of loopx wins over the CLI fallback" instead of the
workflow-local-implying "closer node_modules" phrasing; "OS rejection"
normalized to "runtime rejection" for consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dering, and cleanup caveats

Incorporate review feedback into ADR-0004 ahead of acceptance:

- Warning cardinality: each cleanup warning must contain exactly one of
  three bracketed category tokens ([leave-in-place-refusal],
  [top-level-cleanup-failure], [partial-directory-cleanup-failure]) so
  tests can assert category identity without matching implementation-
  defined prose.
- Options snapshot ordering: options.signal is read before options.env /
  cwd / envFile / maxIterations, making the existing "abort wins over
  options-snapshot exceptions" rule reachable when combined with a
  throwing env getter. Each option field is read at most once per call.
- Non-abort pre-iteration error priority is implementation-defined
  except where already specified (e.g., invalid env shape + missing
  .loopx/ may surface either error).
- Identity-capture-failure regime: document that the single rmdir is
  best-effort and may remove an unrelated empty directory if a same-user
  process swaps the mkdtemp directory before cleanup; this remains
  outside the race-resistant guarantee, as a deliberate trade-off
  against leaking the partial directory on the common identity-capture-
  failure path.
- Promote the run() final-yield cleanup caveat into the Decision Summary
  LOOPX_TMPDIR bullet: cleanup on normal completion is guaranteed only
  once the async generator is driven to settlement; the final yielded
  Output is not itself settlement.
- Soften device/inode wording in normative text to behavioral identity
  language, retaining device/inode as one POSIX implementation strategy.

Update Affected SPEC Sections and Test Recommendations to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, pre-iteration error scoping, and CLI grammar

- Extend AbortSignal precedence to cover target argument validation
  (non-string `target`) and target syntax validation (empty, bare
  colon, etc.) — both previously left ambiguous.
- Add CLI signal precedence paragraph: SIGINT/SIGTERM during the CLI
  pre-iteration phase mirrors programmatic `AbortSignal` precedence.
- Scope the implementation-defined non-abort pre-iteration priority
  rule to the programmatic API only; CLI retains SPEC §7.1's concrete
  ordering.
- Narrow "each option field read at most once" to permit normal
  one-pass enumeration of `options.env` proxies (single `ownKeys` plus
  per-property traps as required by the JS enumeration algorithm);
  additional enumeration passes and getter retries remain forbidden.
- Add explicit pre-first-`next()` consumer-cancellation carve-out:
  `.return()` / `.throw()` on a generator that has not yet had
  `next()` invoked settles per standard async-generator semantics
  without observing signal state, even when the captured signal is
  already aborted. `runPromise()` has no equivalent carve-out.
- Clarify call-time option snapshot vs lazy pre-iteration sequence so
  the "`RunOptions.env` shape validation" placement is unambiguous.
- Pin "no CLI named-argument syntax" as an explicit §4.1/§4.2 affected
  SPEC bullet so the decision is traceable from this ADR.
- Make the cleanup-warning category-token taxonomy and
  at-most-one-warning-per-attempt cardinality an explicit stable
  public testing contract.
- Update §7.3, §9.1/§9.2, and §9.3 affected-SPEC bullets to reflect
  the new carve-outs; add test recommendations covering them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ield, run -h, warning scoping, and invalid options

Addresses acceptance-level blockers raised in review:
- Pre-first-next() consumer cancellation now explicitly wins over all
  captured call-time option snapshot errors (throwing option-field
  getters, invalid env shapes, invalid options values, invalid
  options.signal values), not only already-aborted signals.
- Abort after the final yielded Output is explicitly spelled out as
  Option A — abort still wins until generator settlement; the final
  yield does not commit normal completion.
- The "no CLI named-argument syntax" rule now explicitly preserves the
  existing SPEC §4.2 `run -h` / `--help` short-circuit, both in the
  decision summary and in the §4.1 / §4.2 affected-SPEC entry.
- Cleanup-warning token requirement is scoped narrowly to LOOPX_TMPDIR
  cleanup warnings; other loopx warnings (version-check, unreadable
  package.json, invalid-entry, invalid env-file line, etc.) are
  outside the taxonomy and must not carry these tokens.
- Invalid `options` (null, number, string, array, etc.) and invalid
  `options.signal` (non-AbortSignal-compatible values) are now
  defined: both are captured at call time and surfaced via the
  standard pre-iteration error path; a non-compatible signal does
  not enter the abort-precedence pathway.

Also adds a timing-model terminology note at the start of §2
(call-time capture / pre-iteration surfacing / spawn-time runtime
rejection) and corresponding test recommendations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ith SPEC, broaden env-tier rejection, clarify LOOPX_DELEGATED and hard-link semantics

Review feedback blockers and clarifications:

- Bash `$PWD` / bare `pwd` are no longer claimed to equal
  `LOOPX_PROJECT_ROOT` byte-for-byte in any regime. Per POSIX, Bash
  retains an inherited `PWD` that names the current directory through
  symlinks, so a symlinked-logical ancestor shell can leave the child
  with a `$PWD` that differs from even a canonical
  `LOOPX_PROJECT_ROOT`. Tests must use `pwd -P` / `/bin/pwd` or
  filesystem identity, not `$PWD` / bare `pwd`.
- "version-check error" removed from pre-iteration error-priority
  lists in §1 abort precedence, CLI signal precedence, non-abort
  priority, and the §7.3 / §9.3 affected-SPEC sections and matching
  test recommendation. SPEC §3.2 defines version-check outcomes as
  non-fatal warnings, so version checking is not a competing
  terminal-outcome mode; the ADR now says so explicitly.
- Spawn-time runtime rejection language broadened from "a
  `RunOptions.env` entry" to "any child environment entry from any
  env tier (inherited, env file, `RunOptions.env`)" in §1 Cleanup,
  §1 Timing-model phase 3, affected §7.2, §9.3, §12, and the
  matching test recommendation.
- `LOOPX_DELEGATED` explicitly documented as remaining an internal
  delegation guard outside this ADR's script-protocol-variable
  tier; `RunOptions.env` and env files may supply it to children
  without a loopx-side per-spawn override.
- Hard-link refusal clarified: case 3 applies to top-level path
  replacement only; recursive cleanup under case 4 may unlink
  hard-link entries inside the identity-matched tmpdir, as
  ordinary recursive deletion would.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ping, recursive-removal test, version-check-under-n0 example, and make cwd/envFile validation explicit

- Abort precedence: open paragraph now leads with the "usable signal captured" qualifier; removes the contradictory listing of invalid options / invalid options.signal as items abort can displace (these cases capture no signal and are surfaced as ordinary options-snapshot errors). Ripple fix to the §9.3 Affected SPEC bullet.
- Cleanup-token scoping: tests may now assert that known non-cleanup warnings lack the three bracketed tokens (previously forbidden by the "or lack" clause, which contradicted the normative scoping rule).
- Recursive-removal failure test: split the three category-2 sub-cases so only the lstat-failure and symlink-unlink-failure cases claim "path left in place"; the recursive-removal-failure sub-case no longer promises an intact tmpdir, since partial deletion may have occurred.
- Token-scoping "no-token run" example: replaced version-mismatch-under-maxIterations:0 (never emits, since SPEC §3.2 / §7.1 / §4.2 skip version checking under -n 0) with env-file invalid-line per SPEC §8.1.
- Explicit validation for options.cwd / options.envFile / options.maxIterations values: new subsection defines the string-only requirement for cwd/envFile, captures non-string values at call time, and surfaces via the standard pre-iteration error path. Propagated to the pre-first-next() carve-out list, the non-abort priority enumeration, §9.1/§9.2, §9.3, §9.5 Affected SPEC bullets, and a new test recommendation.

Status remains Proposed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ummary, and LOOPX_DELEGATED role distinction

Addresses acceptance-blocker review feedback: scope the public-contract
surface explicitly, separate startup-reserved from script-protocol-protected
for LOOPX_DELEGATED, tighten runPromise inherited-env timing as
implementation-defined, and make name=value target-validation plus CLI
signal precedence for target syntax explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… -- grammar, §3.2, and cleanup-token escaping

Incorporate reviewer's acceptance-blocker feedback without marking the ADR
Accepted:

- runPromise() inherited-env snapshot timing: make implementation-defined
  relative to return (may capture at sync setup OR async continuation),
  resolving the prior contradiction between "asynchronously after return"
  and the "either outcome is conforming" test license.
- CLI signal-wins precedence: scope explicitly to after the run -h / --help
  short-circuit and usage-level argument parsing. Parser-level usage errors
  (no target, multiple positionals, duplicate -n/-e, unrecognized flags,
  etc.) remain parser-level and are not displaced by signal-wins.
- `--` grammar: reject `--` in any position in `run` outside the help
  short-circuit. No POSIX end-of-options support either; added as a
  Rejected / Deferred Alternative.
- §3.2 — Project Root: add to Affected SPEC Sections to make project-root
  derivation rules (CLI = own process.cwd() without $PWD/realpath;
  programmatic = resolved RunOptions.cwd or process.cwd() at call time)
  explicit.
- Global env-file path resolution: align timing with env-file loading
  (first next() for run(), implementation-defined for runPromise()) so
  XDG_CONFIG_HOME / HOME lookup timing is unambiguous.
- CLI inherited-env test: remove the misleading "concurrent process
  modifies loopx env" claim; make the per-run snapshot mutation test
  programmatic-only, since POSIX forbids external env mutation.
- Cleanup-warning category tokens: require the token in a loopx-controlled
  prefix position AND require implementations to escape / quote
  user-controlled text so TMPDIR-influenced paths cannot introduce a
  second apparent token. Cardinality-of-one remains observable.

Status stays Proposed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…snapshot timing, tighten impl-defined scope and CLI grammar terminology

Address review-pass feedback. Narrow the "implementation-defined by default" sentence so it cannot be read as de-specifying existing SPEC guarantees outside this ADR's change surface. Rewrite the cleanup-warning "Token placement and user-controlled text" rule: the normative requirement is that user-controlled text cannot contribute an additional plain-text occurrence of any token; prefix placement alone is insufficient when paths are also rendered, and merely surrounding user-controlled text in quotes is not sufficient because a quoted string still contains the token substring. Rename the inherited-env subsection to "per-API split, not uniformly 'lazy'" so the text no longer simultaneously claims run()/runPromise() share lazy semantics while also allowing synchronous call-time capture under runPromise(); update cross-references in §2, §7.2, §9.1/§9.2 accordingly. Add an explicit RunOptions.env enumeration algorithm (Object.entries-equivalent semantics, with observable per-key getOwnPropertyDescriptor probes and captured snapshot errors on throwing probe traps), resolving the ambiguity between "non-enumerable own string keys are ignored" and "one-pass enumeration may invoke per-property traps." Split the CLI §4.1 / §4.2 test recommendation so target-validation failures (loopx run name=value) are not lumped under "usage errors"; tests may no longer require a usage-style diagnostic for that form. Add the throwing-addEventListener subcase to the invalid-options.signal rule and clarify that removeEventListener is not part of the compatibility contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…coping, tmpdir parent, option-first priority, duck-typed signal reentrancy

- Fix internal contradiction in §1 "Scope of CLI signal-wins precedence" / §7.3 Affected: pre-handler-installation window no longer claims both "usage error surfaces regardless of signal" and "signals follow POSIX default disposition"; both outcomes are now explicitly conforming and tests must tolerate either.
- Reword CLI signal-wins rule around handler observation rather than OS-level delivery, so implementations that run synchronous pre-iteration work are not required to poll / async-ify to meet the "signal wins" contract.
- Loosen non-cleanup-warning token rule from "must not contain any of the three tokens" to "must not use them as loopx-controlled category markers", permitting incidental occurrences in user-controlled text (env-file lines, paths) and removing a sanitization burden on every non-cleanup warning site.
- Add §1 "Parent directory selection" clarifying that os.tmpdir() is evaluated in the loopx process environment; env-file entries and RunOptions.env do not redirect LOOPX_TMPDIR parent selection.
- Pin project-root-blocking / envFile-path-blocking option errors (non-string / throwing options.cwd / options.envFile) ahead of non-options pre-iteration failures in the programmatic-API priority rule; retain implementation-defined ordering for the remaining non-abort bucket.
- Add §1 "Duck-typed signal reentrancy" rules: synchronous abort-listener invocation or observed-aborted during capture treats signal as aborted; already-aborted real AbortSignal must be observed as aborted; ordering between reading .aborted and registering listener is otherwise implementation-defined for duck-typed signals only.
- Update Scope section to list new implementation-defined caveats (pre-handler-installation signal ordering, delivered-but-unobserved signal vs. synchronous pre-iteration failure, duck-typed signal latitude) and new pinned contract items (project-root-blocking option-error ordering, reserved-as-marker cleanup token scoping).
- Update Test Recommendations to match all of the above.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tightening pass on ADR-0004 surfacing work:

- scope summary rule 5 and detailed non-abort priority section so the
  pinned options.cwd / options.envFile ordering only beats the four
  project-root-dependent failures; relative priority against target
  argument / syntax validation is now explicitly implementation-defined
  (resolves prior contradiction between summary and detailed text)
- carve the inherited-env snapshot timing and call-time option
  snapshotting out of the "pre-iteration runs asynchronously after
  return" rule for runPromise(), matching §2's per-API split
- add cleanup idempotence rule (at most one cleanup attempt per created
  LOOPX_TMPDIR) so per-run warning cardinality is bounded to one across
  racing terminal triggers
- reject function values in RunOptions.env shape, matching the options
  parameter's existing function rejection
- simplify TMPDIR / runPromise() guidance to "before calling
  runPromise()" since user code cannot mutate between call and return
- drop Deno references — SPEC §1 targets only Node.js and Bun
- clarify loopx run name=value grammar classification ("target, not
  named argument" is stable) vs. observable outcome under §7.1, which
  may surface a discovery error first in a project without .loopx/

Status remains Proposed pending separate acceptance.
…tion

Per review feedback: the ADR was doing the work of an ADR plus a future
SPEC patch plus a TEST-SPEC at once. Tightened to focus on the spec
changes themselves so the ADR can fully drive the SPEC.md update and
then retire from the working set.

Specific changes (all per maintainer guidance on the four clarification
points raised in review):

- v1-scope `--` rejection on `loopx run`: framed as parser simplification
  arising from "no named-argument tail," not a permanent product stance.
  Future ADR may revisit alongside a named-argument surface.
- Cleanup warning category tokens demoted to implementation-defined.
  Kept the normative behavior (at most one cleanup attempt per tmpdir,
  at most one warning per attempt) but dropped the public token contract,
  cardinality-by-token-match rules, and user-controlled-text escaping
  rules. Tests assert via provenance, not plain-text token matching.
- `runPromise()` inherited-`process.env` snapshot pinned to call-time
  (synchronous, before return). Removes the previous "implementation-
  defined, bounded by no-later-than-first-spawn" latitude. `run()`
  retains lazy capture at first `next()`. Symmetric with `RunOptions.env`
  and `RunOptions.cwd` capture timing.
- `RunOptions.env` one-pass enumeration / per-trap invocation pattern
  demoted to implementation-defined. Kept "each field read at most once
  per call, no retry after throws" as the SPEC-level guarantee.

Other tightening:
- Removed the ~900-word "Scope of this ADR" preamble that locked
  observable edge-case mechanics as stable contract.
- Removed the standalone "Decision Summary" section (its content was
  duplicated by the §1-§4 decision sections themselves).
- Trimmed Test Recommendations from ~95 bullets to ~9 high-risk edge
  cases. Per ADR-0001 the section is for cases easy to overlook, not
  an exhaustive test plan.
- Promoted CLI grammar from a Decision-Summary bullet to a top-level
  §5 Decision section so the v1-scoping is obvious.
…ace precedence, tmpdir hardening, §7.4

- Broaden pre-first-next() .return()/.throw() carve-out to suppress
  all pre-iteration errors (invalid target, target syntax, discovery,
  env-file loading, target resolution, tmpdir creation) in addition to
  option-snapshot errors, and fix the "without observing signal state"
  wording that conflicted with call-time signal capture.
- Add catch-all terminal-outcome precedence: first trigger observed by
  loopx wins among genuinely racing terminal triggers that no explicit
  precedence rule covers; idempotence and warning cardinality are
  independent of outcome selection.
- Separate RunOptions.env shape validation (programmatic, surfaces
  under maxIterations: 0) from CLI -n 0 behavior (no RunOptions.env
  surface) so the "-n 0 validates env shape" claim is no longer
  misleading.
- Pin tmpdir creation order as mkdtemp → identity capture → mode
  securing, and define per-step creation-failure cleanup
  (no-op / single rmdir / full cleanup-safety routine) in that order.
- Snapshot the tmpdir parent (os.tmpdir() / TMPDIR / TEMP / TMP) on
  the same schedule as the inherited process.env snapshot: synchronous
  for runPromise(), at first next() for run(). Eliminates the
  runPromise() race where a post-return process.env.TMPDIR mutation
  could have silently affected the tmpdir parent.
- Introduce a new §7.4 "Run-scoped temporary directory lifecycle" in
  the Affected SPEC Sections list, covering parent selection, location/
  naming/mode, creation order, identity-fingerprint cleanup safety,
  renamed-away and mount-point behavior, race-resistance scope, absence
  of stale-tmpdir reaping, and settlement-based cleanup on run().
- Qualify the incorrect "no existing workflow script uses this pattern"
  claim: .loopx/shared/dispatch.sh uses cwd-relative ./send-*.sh and
  ./node_modules/.bin/tsx references that require migration.
- Redefine LOOPX_WORKFLOW_DIR as the directory portion of the cached
  absolute discovery-time script path, making the Bash $(dirname "$0")
  equality robust to trailing slashes, symlinks, and lexical
  normalization without recomposing from $LOOPX_PROJECT_ROOT /
  .loopx / $LOOPX_WORKFLOW.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…loopx install

New decision bundled into ADR-0004: loopx install spawns npm install
post-commit in each committed workflow with a top-level package.json.
Adds --no-install opt-out to install-scoped CLI (§4.2). Retires the
existing §10.9 manual-install rule and adds a new §10.10. Rationale
surfaced during analysis of how to optimize existing workflow flows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lzrscg and others added 30 commits April 29, 2026 10:07
Add four new tests closing gaps identified in TEST-SPEC review of
ADR-0004:

- T-API-68t / T-API-69s: pre-first-next() carve-out suppresses SPEC §5.4
  discovery validation failures (sibling workflow same-base-name
  collision, invalid workflow name, invalid script name) on both
  .return() and .throw() consumer-cancellation surfaces. Closes the
  validation sub-paths previously uncovered alongside T-API-68a's
  missing-.loopx/ discovery branch.
- T-API-68u / T-API-69t: pre-first-next() carve-out suppresses unreadable
  local envFile (mode-000) failure, the local-file counterpart to the
  global-unreadable env-file coverage at T-API-68o / T-API-69n. Closes
  the local / global × missing / unreadable matrix on the carve-out
  surfaces.
- T-INST-55i3: selective-install of a top-level symlinked
  workflow-directory entry whose target is bad (broken / cyclic /
  out-of-source) is preflight-atomic-fatal under -w <badalias>, the
  selected-counterpart to T-INST-55i2's unselected-invisibility
  coverage.
- T-INST-55n (n-script-file) sub-case: extends the in-source-root /
  out-of-workflow target-resolution rule to script-entry symlinks
  (ralph/index.sh -> ../shared/lib/helper.sh), pinning that SPEC §10.11's
  source-containment boundary is uniform across script and non-script
  entries.

Open P-0004-08 in SPEC-PROBLEMS.md for the SPEC §5.1 ambiguity around
runtime discovery behavior when symlink resolution fails (broken / cyclic
.loopx/<workflow> directory entries, broken / cyclic script entries, run
vs run -h surfacing, alias-name validation ordering vs failed type
resolution). Per the reviewer recommendation, no tests or SPEC edits are
added until the intended behavior is decided.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ime discovery)

Add three paragraphs to SPEC §5.1's "Symlink policy" subsection specifying
that broken or cyclic symlinks at the workflow-entry or script-entry layer
are silently skipped during runtime discovery, that name validation applies
only after successful classification, and that this rule is runtime-only
(install-source symlink failures remain governed by §10.11). Add T-DISC-40l
through T-DISC-40q as direct conformance pins for the new clauses,
covering broken/cyclic × workflow/script × valid/invalid-name axes. Update
TEST-SPEC.md §9 and Appendix A from "seven" to "eight" resolved
ADR-0004-scoped problems and remove SPEC-PROBLEMS.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…odes)

Open SPEC-PROBLEMS P-0004-09 covering runtime discovery behavior when the
project-root `.loopx` path itself is a failed or non-directory symlink
(broken, cyclic, regular file, FIFO, socket, or other non-directory). SPEC
§5.1's "Symlink policy" specifies that symlinked `.loopx` directories are
followed and spelling-preserved, and the recently-added "Failed symlink
resolution during runtime discovery" paragraph (resolving P-0004-08)
defines failure semantics for the workflow-entry and script-entry layers,
but neither paragraph specifies what happens when the root `.loopx` path
itself is the failed / non-directory symlink. Per reviewer recommendation,
no tests or SPEC edits are added in this cycle; the open problem entry
enumerates the four candidate dispositions and the surfaces that need
clarification (`loopx run <target>`, `loopx run -h`, programmatic
`run()` / `runPromise()`).

Add T-API-65x / T-API-65y for the previously-uncovered abort × version-
warning combined intersection on `runPromise()` and `run()` surfaces.
Pre-aborted signal plus an unsatisfied workflow-level loopx semver range
on the targeted workflow surfaces the abort error with no child spawned
and no tmpdir created; the test deliberately does not assert on stderr
version-warning emission because ADR-0004 §1 places version checking
outside the SPEC §9.3 abort-precedence dispatch entirely. Trace the new
tests in the SPEC 3.2 / 9.1 / 9.2 / 9.3 / 9.5 traceability matrix rows.

Update TEST-SPEC.md §9 from "eight" to "nine" ADR-0004-scoped entries and
note that P-0004-09 remains open at the close of this cycle (the first
eight remain resolved by SPEC clarifications described in the same
section).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Amend SPEC.md §5.1 with a new "Project-root .loopx entry failures during
runtime discovery" two-paragraph block specifying that absent / broken-symlink /
cyclic-symlink / symlink-to-non-directory / non-directory-non-symlink root
entries are treated as having no usable .loopx/ directory, and append a
project-root cache-clarification paragraph specifying that successful root
resolution caches discovery results for the run with later retargeting not
causing re-discovery. Broaden §7.2's "Missing .loopx/ directory" bullet to
"Missing or unusable" with a §5.1 cross-reference, and broaden §11.2's
run-help missing-.loopx/ bullet to cover the not-usable-as-a-directory case.
This is candidate (a) of the four resolution paths previously enumerated in
SPEC-PROBLEMS P-0004-09.

Update TEST-SPEC.md §9 and Appendix A to reflect that all nine ADR-0004-scoped
problems are now resolved (fixing the prior internal contradiction where §9
correctly said P-0004-09 was open while Appendix A said no ADR-0004 ambiguities
were open). Add the T-DISC-49a–T-DISC-49g test family covering the four
project-root failure modes (broken symlink, cyclic symlink, symlink-to-non-
directory, non-directory non-symlink — the latter exercised across regular
file, FIFO, and socket sub-cases) parameterized across the four observation
surfaces (loopx run <target>, loopx run -h, run(), runPromise()), plus a
cache-behavior test (T-DISC-49g) pinning the project-root cache-clarification
paragraph across two sub-cases (newly-reachable workflows not discovered;
cached paths failing as spawn failures after retargeting).

Delete SPEC-PROBLEMS.md since no ADR-0004-scoped SPEC ambiguities remain open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add programmatic-API counterparts for runtime broken/cyclic symlink
discovery (T-DISC-40r-w) and project-root .loopx retargeting cache
behavior (T-DISC-49h) to mirror existing CLI-only coverage on
run() / runPromise(). Add T-INST-112l / T-INST-112m for the
.gitignore safeguard skip-before-lstat dispatch under no-package.json
and malformed-package.json conditions, plus T-INST-112n for the
gitignore-lstat-fail seam composed with a pre-existing regular
.gitignore (proves the "make no further changes" rule). Broaden the
existing known-gap note for CLI cross-iteration reuse to also cover
global env-file path resolution. Refresh the SPEC 5.1, 5.3, and 10.10
traceability matrix entries to cite the new test IDs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds five test additions and an organizational change in response to
ADR-0004 review feedback:

- T-INST-55z / 55za / 55zb / 55zc / 55zd: extend §10.11 install-source
  symlink coverage to root-level single-workflow classification, source-root
  entry-type policy, and nested (inside-`lib/`) non-script materialization /
  rejection branches.
- T-API-61t4: prototype-side `Proxy` `ownKeys` trap on outer options is never
  enumerated, closing the for-in / prototype-chain enumeration axis distinct
  from T-API-61t3's own-side proxy axis.
- Eight new T-TMP-12 sub-cases for throwing-getter / throwing-trap option
  snapshot failures, asserting no `LOOPX_TMPDIR` is created across all
  recognized fields and `options.env` proxy traps.
- T-INST-119c: real-time stdout / stderr streaming on the non-zero-exit
  failure path, closing the {success, failure} × {final, real-time} ×
  {stdout, stderr} matrix on SPEC 10.10's "neither buffers" contract.
- §9 renamed from "Pending Spec Decisions" to "Resolved ADR-0004 SPEC
  Clarifications (Historical)" with explicit framing that no ADR-0004-scoped
  SPEC ambiguities are open at the close of this cycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ion priority

Reword T-API-70 so it asserts only that runPromise() rejects with
"some applicable pre-iteration error" rather than "both rejections
happen". SPEC 9.3 leaves the relative priority among non-abort
pre-iteration errors largely implementation-defined (only the four
project-root-dependent failures are pinned behind project-root-blocking
option errors), so the previous wording could be misread as requiring a
specific winner or multiple surfaced rejection reasons.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add six new test cases closing the only ADR-0004-specific
compositional gaps identified in the review:

- T-INST-111d / T-INST-111e: multi-workflow `--no-install` coverage,
  with and without `-w` selective-install, pinning SPEC §10.10's
  "for every selected workflow" wording.
- T-INST-55v3 / T-INST-55x2: source `package.json` symlinks with
  targets in source root but **outside** the selected workflow
  (regular-file and directory variants), pinning SPEC §10.11's
  source-containment boundary at the install source root, not the
  workflow directory.
- T-INST-112a4 / T-INST-112a5: source `.gitignore` symlinks with
  the same cross-workflow-boundary target shapes (regular-file and
  directory variants), pinning the same boundary on the §10.10
  safeguard surface.

No SPEC.md edits and no SPEC-PROBLEMS.md entry: review found no
ADR-0004-scoped ambiguity requiring a SPEC change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Re-cast T-INST-112n's mode-bit assertion via the
  gitignore-make-unreadable;gitignore-lstat-fail seam composition so the
  conformance anchor is a seam-set 000 mode rather than a source-derived
  0644, matching SPEC 10.10's silence on install/copy phase mode-bit
  preservation. Adds the same harness-side mode-restoration discipline
  used by T-INST-112k and gates on process.getuid() !== 0.
- Add T-DISC-49i / T-DISC-49j / T-DISC-49k covering project-root .loopx
  removal and real-directory replacement mid-run on the CLI and
  programmatic surfaces, completing the {retargeting / removal /
  replacement} × {CLI / run() / runPromise()} matrix for SPEC 5.1's
  cache-clarification clause that previously only had retargeting
  coverage via T-DISC-49g / T-DISC-49h.
- Add T-TMP-38b-run / T-TMP-38b2-run as run() generator-surface
  counterparts to T-TMP-38b / T-TMP-38b2, closing the warning-cardinality
  axis for the non-zero-exit-vs-abort race symmetrically across both
  observation orders on the lazy-capture surface.
- Add T-API-61t5 covering the descriptor-enumeration half of SPEC 9.5's
  "outer-object ownKeys / descriptor enumeration traps not invoked"
  guarantee — observably distinct from the ownKeys axis already pinned
  by T-API-61t3 / T-API-61t4. Also adds T-API-61t4 to the SPEC 9.5
  appendix entry which had been missed previously.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- T-INST-112n: rewrite the "Mode-restoration division of responsibility"
  paragraph to spell out the assertion-ordering sequence under mode 000
  (lstat-only mode check before harness chmod; harness chmod; content-read
  after harness chmod). The prior wording asserted that the harness chmod
  ran after both (e) and (f) had read the on-disk content and mode bits,
  but under non-root assertion (e) cannot read a mode-000 file before the
  harness relaxes its mode bits. Annotate the matrix entry with a pointer
  to the test entry's full ordering rationale.

- Open two ADR-0004-scoped SPEC-PROBLEMS entries:
    - P-0004-10: .gitignore safeguard lstat-failure no-mutation guarantee
      (SPEC 10.10 lstat-failure / write-failure bullet does not explicitly
      say loopx makes no further changes to the .gitignore path; T-INST-112h
      and T-INST-112n encode that natural-reading "no further changes" rule).
    - P-0004-11: auto-install package.json validation timing — committed-
      state vs. cached preflight result (SPEC 3.2 / 10.10 do not specify
      whether the post-commit auto-install dispatch re-evaluates the
      committed package.json or uses a preflight-cached result, while
      T-INST-113i / 113j / 113k assume re-evaluation via the
      package-json-replace-with-{symlink,fifo,socket}: seams).
  Update Appendix A's "Tests covering SPEC ambiguities" preface and the
  section 9 historical-resolutions preface to reflect the two newly opened
  problems.

- Appendix A traceability matrix updates:
    - 10.10 row: add T-INST-111d, T-INST-111e, T-INST-112a4, T-INST-112a5,
      T-INST-55v3, T-INST-55x2 (each with short coverage descriptions).
    - 10.11 row: add T-INST-55i2, T-INST-55i3, T-INST-55v3, T-INST-55x2,
      T-INST-110h, T-INST-112a2, T-INST-112a3, T-INST-112a4, T-INST-112a5
      to the front-of-row test list (previously mentioned only in trailing
      prose).
    - 9.1 row: split the ambiguous "T-API-64b–64e" range into explicit
      "T-API-64b, T-API-64b2, T-API-64c, T-API-64d, T-API-64e" and add
      T-API-65u2 / T-API-65u3 / T-API-68s2 / T-API-69r2 with descriptions.
    - 9.5 row: split "T-API-64–64e" into explicit IDs including
      T-API-64b2 and add T-API-65u2 / T-API-65u3 / T-API-68s2 / T-API-69r2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P-0004-10 (.gitignore safeguard lstat-failure no-mutation guarantee)
resolved by amending SPEC §10.10's lstat-failure / write-failure bullet
to add explicit no-further-mutation wording; T-INST-112h and T-INST-112n
become direct conformance pins.

P-0004-11 (auto-install package.json validation timing) resolved by
amending SPEC §3.2's "Install" warning-timing bullet (multi-evaluation-
point framing) and SPEC §10.10's "Malformed package.json" bullet
(committed-state dispatch); T-INST-113i / T-INST-113j / T-INST-113k
become direct conformance pins.

TEST-SPEC.md section 9 and Appendix A updated to record the
resolutions; SPEC-PROBLEMS.md deleted as no entries remain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address four coverage gaps identified during TEST-SPEC review:

- Add T-INST-113m and T-INST-113n with new section 1.4 seams
  (`package-json-remove:`, `package-json-replace-with-valid:`) to pin
  the SPEC §10.10 P-0004-11 "committed filesystem state at that moment,
  not solely a source/preflight-cached validation result" rule in both
  directions: committed-absent (skip) and committed-valid-after-malformed-
  preflight (proceed).

- Add T-INST-112o with new section 1.4 seam
  (`gitignore-partial-write-fail:`) to pin SPEC §10.10's "Any partial
  filesystem effect already produced by a failed write is left for user
  remediation" clause on the partial-bytes-on-disk branch, complementing
  T-INST-112c's no-bytes-written coverage.

- Add T-API-10c7-c10 covering duck-typed signal × `maxIterations: 0` on
  both run() and runPromise() — closing the {real-aborted /
  duck-aborted-at-capture / duck-reentrant-listener-fires-only} ×
  `maxIterations: 0` × {run() / runPromise()} matrix per SPEC §9.5
  duck-signal abort treatment × SPEC §9.3 zero-iteration-short-circuit.

- Open SPEC-PROBLEMS P-0004-12 tracking the scope of SPEC §7.4's
  "no stale-tmpdir reaping at startup" rule on programmatic surfaces.
  T-TMP-32 (CLI surface) remains a direct conformance pin; T-TMP-32a /
  T-TMP-32b are flagged observational pending SPEC clarification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…to TEST-SPEC.md

Resolve SPEC-PROBLEMS P-0004-12 by amending SPEC §7.4's startup-non-reaping
clause to enumerate the run-creation surfaces explicitly: "loopx does not
reap stale tmpdirs during CLI startup, CLI \`loopx run\` setup, or any
per-run setup performed for \`run()\` / \`runPromise()\`," with a normative
second sentence specifying that a run setup creates only its own
\`mkdtemp\` directory and does not scan / validate / remove pre-existing
\`loopx-*\` entries under the parent. This is Resolution path (a) of the
three resolution paths previously identified at SPEC-PROBLEMS P-0004-12 —
matches the cross-surface tmpdir-creation parity already established at
SPEC §9.1 / §9.2 / §7.4.

Reclassify T-TMP-32a (\`runPromise()\`-surface) and T-TMP-32b (\`run()\`-
surface) from observational coverage of the broad reading to direct
conformance pins under the amended SPEC §7.4 clause; T-TMP-32 (CLI) was
already a direct conformance pin under all three resolution paths.

Move P-0004-12 from open-problem to the resolved/historical section in
TEST-SPEC §9 and Appendix A. Delete SPEC-PROBLEMS.md since no ADR-0004-
scoped problems remain open.

Fix two editorial issues in T-TMP-12 prose flagged by the same review:
(1) replace misleading "lazy first-\`next()\` snapshot under \`run()\`;
eager call-site snapshot under \`runPromise()\`" wording — which conflated
option-snapshot timing with inherited-env / tmpdir-parent snapshot
timing — with explicit "option values captured at the call site for both
surfaces; under \`run()\` the captured error is surfaced on first
\`next()\`" framing; (2) update stale "seventeen distinct pre-iteration
failure modes" sub-case counts in T-TMP-12 opening prose and T-TMP-12-cli
prose to "twenty-five sub-cases," matching the closing summary's count.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…TEST-SPEC.md

Open SPEC-PROBLEMS P-0004-13 tracking SPEC §10.11's underspecified
behavior for selected install-source symlinks whose targets resolve
inside the install source root but are neither regular files nor
directories (FIFO, socket, block device, character device). The
materialization clauses enumerate only file and directory targets, and
the rejection clause enumerates only broken / cyclic / out-of-source
failure classes — leaving in-source non-file / non-directory targets
unspecified. The §10.11 "package.json source symlink precedence"
paragraph has the same gap. Three resolution paths documented; preferred
direction is to extend the rejection rule to cover in-source non-file /
non-directory targets, mirroring the broken / cyclic / out-of-source
rejection structure.

Add three known-gap annotations to TEST-SPEC.md flagged by the review:

- T-DISC-49-block-char-device (project-root `.loopx` block / character
  device — SPEC §5.1 "or other non-directory entry" enumeration at the
  project-root layer): added inline after T-DISC-49k and referenced in
  Appendix A's §5.1 row, mirroring the precedent set by T-TMP-45,
  T-INST-112-block-char-device, and T-VER-28-block-char-device for the
  same `mknod(2)` / `CAP_MKNOD` privilege requirement.

- T-INST-116o-other-categories (SPEC §10.10 active-child × prior-failure
  -suppression × signal observed for prior-failure categories other
  than `npm install` non-zero exit — i.e., `.gitignore` safeguard
  failure and `npm install` spawn failure prior-failure categories not
  directly covered): the existing T-INST-116o "Representative-coverage
  scope" paragraph already explained the limitation but had not been
  formalized in the explicit known-gap inventory; this commit promotes
  it to a dedicated known-gap entry inline after T-INST-116o2 and adds
  a reference to Appendix A's §10.10 row. Deterministic coverage would
  require ordinal-targeted FAULT seam variants (e.g., `gitignore-write-
  fail-first`, `npm-spawn-fail-first`) to be added to section 1.4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…to TEST-SPEC.md

Extend SPEC §10.11 to reject install-source symlinks whose targets resolve
in-source to entries that are neither regular files nor directories
(FIFO, socket, block/character device, or other non-regular non-directory
entry), per Resolution A. Mirror the rejection in the `package.json`
source-symlink precedence paragraph. Add T-INST-55ze / T-INST-55zf /
T-INST-55zg as direct conformance pins (with a new section 1.4
`LOOPX_TEST_INSTALL_FAULT=source-target-replace-with-{fifo,socket}:`
seam since FIFOs/sockets aren't carried through git/tarball transports),
list the block/character device sub-cases as known gaps, and update
section 9 / Appendix A / section 1.3 to reflect P-0004-13's resolution.
Delete SPEC-PROBLEMS.md (no remaining open ADR-0004 problems).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add T-TMP-12-cli-missing-loopx (CLI counterpart for missing .loopx/
  discovery failure not creating a tmpdir).
- Add T-TMP-12-env-file-unreadable / T-TMP-12-cli-env-file-unreadable
  for the no-tmpdir-creation contract on EACCES local envFile failures
  (companions to the existing missing-file and global-env-unreadable
  sub-cases).
- Add LOOPX_TEST_AUTOINSTALL_FAULT=package-json-make-unreadable seam
  and rewrite T-INST-113a / T-INST-113c2(i) / T-INST-113l(l-unreadable)
  to use it, so the unreadable-package.json failure mode is exercised
  on the SPEC §10.10 dispatch-time committed state rather than relying
  on source mode 000 surviving SPEC §10.7 stage-then-commit.
- Add narrowed no-progress-indicator assertions to T-INST-119b and
  T-INST-119c so the failure-path coverage matches T-INST-119(c).
- Extend T-INST-55l2 with cycle / out-of-source-file /
  out-of-source-directory variants so the SPEC §10.11 vs §10.10
  precedence pin holds across all four failure classes for the
  special .gitignore filename.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Synchronize the high-level known-gap inventory in §1.3 and Appendix A
§8.1 with the inline known-gap notes in §4.9 (global-env-file
path-resolution × cross-iteration reuse) and §4.10
(T-INST-116o-other-categories — active-child × prior-failure-suppression
for non-npm-non-zero categories). No SPEC change; no test removal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add static-duck-aborted carve-out variants (T-API-68v / T-API-69u),
the non-`run` CLI startup non-reaping test (T-TMP-32c), programmatic
parity tests for the no-warning symlink cleanup branches (T-TMP-34a/b
top-level, T-TMP-37d/e nested), parameterize T-INST-120e across the
five SPEC 3.2 malformed-`package.json` causes, and cross-reference
T-INST-60q/r/s/t from T-INST-111c for matrix completeness.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fix T-TMP-32c c-version sub-case to use `loopx version` only — `loopx
  --version` is not valid per SPEC §4.2 which defines `-h` / `--help` as
  the only recognized top-level flag.
- Add c-parser-error sub-case to T-TMP-32c covering the SPEC §7.4 "CLI
  startup" non-reaping rule on the parser-error dispatch path
  (structurally distinct from the help / version / no-args success
  short-circuits), preserving the parser-error coverage that was
  implicit in the prior incorrect `loopx --version` invocation.
- Update Appendix A §7.4 row to explicitly list T-TMP-32c, T-TMP-34a,
  T-TMP-34b, T-TMP-37d, T-TMP-37e (already covered in the body).
- Update Appendix A §8.3 and §9.5 rows to explicitly list T-API-58b,
  T-API-58c, T-API-58d, T-API-58f, T-API-58f2 (previously only
  partially referenced in prose within neighbouring entries).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Relax over-specified diagnostic-category assertions in T-INST-55ze/zf/zg,
T-VER-28, T-VER-28c, T-VER-28m, T-INST-113h, and T-TMP-12f5: SPEC requires
rejection / warning behavior but does not normatively require category-
distinct diagnostic wording. Add boolean / symbol / bigint primitive
variants to RunOptions.env whole-env shape (T-API-53e/f/g) and outer
options shape (T-API-61c2, T-API-61e2) so the matrix-completion claims
hold across the typeof axis.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add four tests closing residual gaps in the ADR-0004 surface:

- T-INST-55zj: source-root single-workflow script symlink rejected
  preflight when target is FIFO/socket; closes the SPEC §10.11
  rejection-rule axis the existing in-workflow / multi-workflow alias
  / package.json / symlinked-ancestor tests left uncovered.
- T-INST-55l3: bad source `.gitignore` symlink resolving in-source to
  FIFO/socket is rejected by §10.11 preflight before §10.10 safeguard
  pathway runs; pins the precedence invariant on the special filename
  for the FIFO/socket failure class to match T-INST-55l2's broken /
  cyclic / out-of-source coverage.
- T-TERM-05: CLI surface, child-exit observed first, signal arrives
  before outcome dispatch — surfaced exit code reflects the
  script-failure outcome (1), not the second-observed signal (143);
  asserts cleanup-warning cardinality remains at most one.
- T-INST-119d: byte-shape hardening for SPEC §10.10 npm-passthrough
  contract via leading/trailing whitespace, embedded tabs, no
  trailing newline, and multi-byte UTF-8 — catches trimming /
  line-normalization bugs that line-marker tests miss.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix bookkeeping/wording issues raised in TEST-SPEC review:
- Extend P-0004-13 traceability in section 1.3, section 9, and Appendix
  A's 10.11 row to include T-INST-55l3 and T-INST-55zj (now six axes
  rather than four, with matching block/character device known-gap
  entries).
- Clarify that the source-target-replace-with-{fifo,socket} seams in
  section 1.4 must run before install-time workflow/script discovery
  and §10.11 validation (not merely before a per-workflow validation
  pass), and list T-INST-55zi/55l3/55zj as additional consumers.
- Add T-INST-119d to Appendix A's 10.10 row.
- Add the §7.4 sub-step 2 non-recursive rmdir guarantee to Appendix
  A's 7.4 known-gap list for consistency with section 1.3 / 4.7.
- Fix T-VER-28ah's taxonomy phrase: a missing-script during qualified
  goto resolution is its own §7.4 cleanup trigger, not a child-launch
  failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Soften T-INST-119d to remove the overclaim that its byte-exact
substring assertions catch final-newline-injection / tail-byte-mutation
bugs. Under the test's existing scope note (auxiliary loopx output is
admitted before/after the npm payload), a substring match cannot prove
no byte was appended after the npm payload. Replace that bug-class with
the line-buffered drop-partial-line bug class that the no-trailing-newline
payload actually does catch, and add an explicit limitation note plus a
matching index-row qualifier pointing to the future SPEC §10.10
exact-stream-equality clarification path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant