@open-agent-toolkit/cli 0.2.76 → 0.2.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/assets/agents/recon-worker.md +12 -12
  2. package/assets/bundle-metadata.json +1 -1
  3. package/assets/docs/workflows/skills/recon.md +53 -27
  4. package/assets/public-package-versions.json +4 -4
  5. package/assets/skills/explainer-kit/SKILL.md +1 -1
  6. package/assets/skills/explainer-kit/scripts/bundle.mjs +26 -2
  7. package/assets/skills/explainer-kit/scripts/record.mjs +26 -3
  8. package/assets/skills/explainer-kit/scripts/verify.mjs +26 -3
  9. package/assets/skills/oat-explainer-kit/SKILL.md +1 -1
  10. package/assets/skills/oat-explainer-kit/scripts/check-terminal-outcome.mjs +25 -5
  11. package/assets/skills/oat-project-complete/SKILL.md +1 -1
  12. package/assets/skills/oat-project-complete/scripts/consume-persisted-recap-intent.mjs +26 -6
  13. package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +26 -6
  14. package/assets/skills/oat-project-complete/scripts/finalize-synced-archive.mjs +7 -0
  15. package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +26 -5
  16. package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +26 -5
  17. package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +26 -6
  18. package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +26 -6
  19. package/assets/skills/oat-project-complete/scripts/resolve-synced-archive-entry.mjs +26 -5
  20. package/assets/skills/oat-project-complete/scripts/validate-durable-archive-receipt.mjs +7 -0
  21. package/assets/skills/oat-project-complete/scripts/validate-nonarchive-lifecycle-receipt.mjs +126 -95
  22. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  23. package/assets/skills/oat-project-implement/scripts/capture-dirty-tree.mjs +3 -0
  24. package/assets/skills/recon/SKILL.md +37 -19
  25. package/assets/skills/recon/references/packet-contract.md +33 -30
  26. package/assets/skills/recon/references/profiles.md +18 -15
  27. package/assets/skills/recon/references/worker-contract.md +87 -7
  28. package/assets/skills/recon/scripts/create-review-brief.mjs +2 -5
  29. package/assets/skills/recon/scripts/lib/cli-entry.mjs +21 -0
  30. package/assets/skills/recon/scripts/lib/contracts.mjs +144 -38
  31. package/assets/skills/recon/scripts/lib/routing.mjs +2 -5
  32. package/assets/skills/recon/scripts/lib/validated-run.mjs +1 -1
  33. package/assets/skills/recon/scripts/prepare-routing.mjs +2 -2
  34. package/assets/skills/recon/scripts/reconcile-ledger.mjs +269 -2
  35. package/assets/skills/recon/scripts/render-packet.mjs +2 -5
  36. package/assets/skills/recon/scripts/validate-artifact.mjs +2 -5
  37. package/assets/skills/recon/scripts/validate-packet.mjs +73 -23
  38. package/package.json +2 -2
@@ -1,16 +1,17 @@
1
1
  ---
2
2
  name: recon-worker
3
- version: 1.0.1
3
+ version: 1.0.2
4
4
  description: Executes one bounded recon packet assignment as a non-interactive leaf worker and writes exactly one declared artifact.
5
5
  tools: Read, Bash, Grep, Glob, Write, WebSearch, WebFetch
6
6
  color: cyan
7
+ is_background: true
7
8
  ---
8
9
 
9
10
  ## Role
10
11
 
11
12
  You are a non-interactive recon leaf worker. Execute exactly one assignment in
12
- one declared mode: `map`, `gather`, `compile`, `verify`, `adversary`, `coverage`,
13
- or `reconcile`. No other mode is valid.
13
+ one declared mode: `map`, `gather`, `compile`, `verify`, `adversary`, or
14
+ `coverage`. No other mode is valid.
14
15
 
15
16
  The controller maps approved manifest waves to this closed vocabulary:
16
17
 
@@ -22,7 +23,6 @@ The controller maps approved manifest waves to this closed vocabulary:
22
23
  | `semantic-verification` | `verify` |
23
24
  | `adversarial` | `adversary` |
24
25
  | `coverage` | `coverage` |
25
- | `reconciliation` | `reconcile` |
26
26
  | `redundant-gather` | `gather` |
27
27
  | `redundant-verification` | `verify` |
28
28
  | `contradiction-resolution` | `adversary` |
@@ -75,7 +75,9 @@ canonical claim status.
75
75
 
76
76
  Consume only the designated dossiers. Deduplicate their findings into a
77
77
  provisional claim-ledger candidate and cite direct input artifacts. Do not
78
- reopen excluded sources or invent evidence.
78
+ reopen excluded sources or invent evidence. Every persisted display excerpt
79
+ must be an exact contiguous substring of the cited source, except for the
80
+ declared `redacted-exact` representation.
79
81
 
80
82
  ### `verify`
81
83
 
@@ -99,13 +101,6 @@ Compare the declared scope and questions with the permitted ledger projection.
99
101
  Emit covered items, missing areas, and material gaps. Do not read gatherer
100
102
  reasoning.
101
103
 
102
- ### `reconcile`
103
-
104
- Apply permitted review dispositions and contradiction outcomes to a new ledger
105
- candidate. Preserve the prior revision, legal state transitions, evidence
106
- links, qualifications, and unresolved issues. Never invent evidence or update
107
- the existing ledger in place.
108
-
109
104
  ## Output
110
105
 
111
106
  Write one JSON artifact using only the supplied closed schema's fields. A
@@ -121,6 +116,11 @@ Do not add a second mode field or any other unknown field. The controller
121
116
  validates `waveId` and `mode`, or `reviewerLane` and `reviewKind`, against the
122
117
  approved manifest wave before promoting the artifact.
123
118
 
119
+ Before returning, run the supplied deterministic validator against the sole
120
+ unpromoted `writePath`. Correct an invalid candidate within this same accepted
121
+ task and validate it again. If it remains invalid, return terminal
122
+ `PASS_FAILED`; the controller must not retry or replace the worker.
123
+
124
124
  Return only the artifact path and compact outcome. Do not return source bodies,
125
125
  worker reasoning, or dossier contents to the controller.
126
126
 
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "oatVersion": "0.2.76"
3
+ "oatVersion": "0.2.79"
4
4
  }
@@ -69,19 +69,21 @@ context source, output directory, or strict authority:
69
69
  The default profile is `standard`. Each homogeneous wave has an independently
70
70
  selected and approved exact target. Bounded mapping, extraction, citation
71
71
  reopening, counterexample search, and mechanical dossier compilation start with
72
- economical qualified routes. A wave that genuinely requires interpretation or
73
- reconciliation judgment can use a stronger approved target without raising the
74
- cost of unrelated evidence waves.
72
+ economical qualified routes. Reconciliation is deterministic controller work
73
+ rather than a separately approved worker target.
75
74
 
76
75
  | Profile | Use it for | Required assurance work | Claim ceiling |
77
76
  | ---------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------- |
78
77
  | `quick` | Bounded orientation | Mapping, gathering, compilation, schema validation, and locator validation; no independent semantic pass by design | `supported` |
79
- | `standard` | Load-bearing evidence | Quick work plus blind semantic verification, adversarial review, coverage, and reconcile | `verified` |
78
+ | `standard` | Load-bearing evidence | Quick work plus blind semantic verification, adversarial review, coverage, and deterministic reconciliation | `verified` |
80
79
  | `thorough` | Expensive failure or correlated blind spots | Standard work plus required redundant gathering and redundant verification | `verified` |
81
80
 
82
81
  A thorough run may additionally predeclare an optional, condition-bound
83
82
  `contradiction-resolution` evidence wave. It runs only when its approved
84
83
  predicate triggers and is not required when that branch is not triggered.
84
+ Gather and redundant-gather waves may partition source work across lanes. Each
85
+ typed review wave uses exactly one lane so deterministic reconciliation has one
86
+ result per review kind, at the fixed path bound by the approved manifest.
85
87
 
86
88
  The approval manifest shows each wave's exact target, task class, class floor,
87
89
  selection rationale, adaptive lane count, conditional rule, concurrency,
@@ -93,11 +95,16 @@ change requires renewed approval; there is no silent substitution after
93
95
  approval.
94
96
 
95
97
  Before any worker launches, recon confirms that the live launch surface can run
96
- the approved role, model, effort, and authority level. If it cannot, the run
97
- stays at `awaiting-approval` with a provider/dispatch diagnostic and nothing is
98
- launched. Deadlines are approved execution limits chosen for the task class,
99
- not short watchdogs; an accepted lane is never interrupted by hand because a
100
- displayed deadline elapsed.
98
+ the approved role, model, effort, and authority level. A materialized Cursor
99
+ role file is configuration, not evidence that the current Task catalog exposes
100
+ that role; the live catalog controls eligibility, with the approved generic
101
+ fallback used when needed. If the launch surface cannot run the selected
102
+ target, the run stays at `awaiting-approval` with a provider/dispatch diagnostic
103
+ and nothing is launched. Every Cursor leaf runs in the background so later
104
+ parent-chat messages cannot interrupt multi-minute evidence work. Deadlines are
105
+ approved execution limits chosen for the task class, not short watchdogs; an
106
+ accepted lane is never interrupted by hand because a displayed deadline
107
+ elapsed.
101
108
 
102
109
  ### Preview and Check Routing
103
110
 
@@ -117,29 +124,30 @@ compare the constructed target with that wave's approved exact target:
117
124
  node .agents/skills/recon/scripts/prepare-routing.mjs --manifest manifest.json --wave gather-1 --check-target candidate-target.json
118
125
  ```
119
126
 
120
- These commands validate structure, approval integrity, and exact identity. They
121
- do not launch a worker, rank model names, attest actual runtime identity, or
122
- measure cost.
127
+ These commands validate structure, approval integrity, and exact identity. Like
128
+ the other recon command-line helpers, they use a shared realpath-aware entry
129
+ check, so direct and symlinked invocations execute identically. They do not
130
+ launch a worker, rank model names, attest actual runtime identity, or measure
131
+ cost.
123
132
 
124
133
  ### Bounded Conditional Evidence
125
134
 
126
- Standard and thorough may predeclare finite conditional
127
- `contradiction-resolution` evidence waves. A completed predecessor can trigger
128
- one such wave to seek discriminating evidence; accepted failure, cancellation,
135
+ Standard and thorough may each predeclare at most one conditional
136
+ `contradiction-resolution` evidence wave. A completed predecessor can trigger
137
+ that wave to seek discriminating evidence; accepted failure, cancellation,
129
138
  timeout, or missing output cannot trigger a replacement. Triggered,
130
139
  not-triggered, and unresolved dispositions remain visible. Both branches feed
131
- the same single mandatory terminal reconciliation.
140
+ the same deterministic controller reconciliation.
132
141
 
133
142
  A triggered condition must cite complete, digest-bound evidence from an approved
134
143
  predecessor in the same run. Evidence copied from another run cannot activate
135
144
  the destination wave and fails packet validation with
136
145
  `CONDITION_EVIDENCE_RUN_MISMATCH`.
137
146
 
138
- If a need for stronger reconciliation judgment is foreseeable, select that one
139
- terminal target before approval. If it appears only after approval, recon
140
- preserves completed evidence and returns an unresolved, out-of-envelope gap for
141
- renewed approval or a new run. It never mutates the target, silently retries,
142
- or launches a second reconciliation.
147
+ If reconciliation exposes a need for new semantic judgment, recon preserves
148
+ completed evidence and returns an unresolved, out-of-envelope gap for renewed
149
+ approval or a new run. It never converts deterministic reconciliation into a
150
+ worker wave or silently retries an accepted worker.
143
151
 
144
152
  ## Destination Precedence
145
153
 
@@ -173,8 +181,8 @@ non-publishable candidate generation withdraws any existing `packet.md`:
173
181
  - `claims.json` is the canonical claim ledger.
174
182
  - `manifest.json` records request, source, approved execution envelope, and
175
183
  gap provenance.
176
- - `reviews/` contains compact semantic, adversarial, coverage, and
177
- reconciliation evidence.
184
+ - `reviews/` contains compact semantic, adversarial, coverage, and the
185
+ controller-produced reconciliation record.
178
186
  - `raw/` contains worker dossiers, candidate artifacts, and safe failure
179
187
  diagnostics. It is not normal consumer input.
180
188
 
@@ -198,6 +206,20 @@ ledger must reference every completed primary and redundant gather dossier by
198
206
  exact path and digest before review briefs are created. Missing provenance fails
199
207
  with `MISSING_THOROUGH_GATHER_LEDGER_INPUT`.
200
208
 
209
+ Compile workers copy each evidence excerpt as one contiguous exact substring of
210
+ the reopened source. A source-required redaction may replace only the sensitive
211
+ span while preserving the surrounding bytes and recording the redaction. Recon
212
+ does not repair stale locators after compilation. Each accepted worker validates
213
+ its own closed artifact schema once, within the same accepted task, before
214
+ returning. An invalid accepted artifact is terminal; the controller neither
215
+ retries the schema nor substitutes another worker.
216
+
217
+ When an accepted lane has already written a schema-valid, lane-matching artifact
218
+ at its approved path, that durable artifact remains the result even if the
219
+ provider stream later closes with an error. Recon records the stream failure as
220
+ a provider diagnostic and does not relaunch the lane. A missing or invalid
221
+ artifact remains a failed pass.
222
+
201
223
  The ledger compiler and packet validator enforce categorical referential
202
224
  integrity on `synthesis.keyClaimIds` and `synthesis.unresolvedQuestionIds`. Any
203
225
  reference to an unknown claim or question ID fails validation with
@@ -228,10 +250,14 @@ the caller assesses sufficiency and conclusions. Stronger profiles can promote
228
250
  claims only when their required independent review artifacts validate and were
229
251
  written by approved lanes.
230
252
 
231
- When an independent semantic review rejects a proposed claim, the reconciler
232
- transitions it to `unsupported` rather than deleting it. This preserves the
233
- claim statement, cited evidence, and transition history in `claims.json`, and
234
- renders the finding under Contradictions and Qualifications.
253
+ When an independent semantic review rejects a proposed claim, deterministic
254
+ controller reconciliation transitions it to `unsupported` rather than deleting
255
+ it. The controller validates the digest-bound input ledger and required review
256
+ set, writes `raw/drafts/claims-v2.json` and
257
+ `reviews/reconciliation.json`, validates both, then atomically promotes the
258
+ candidate ledger to `claims.json`. This preserves the claim statement, cited
259
+ evidence, and transition history and renders the finding under Contradictions
260
+ and Qualifications.
235
261
 
236
262
  An adversarial challenge transitions a claim to `contested`. A run with
237
263
  contested claims may publish as `complete` when all declared questions and
@@ -1,6 +1,6 @@
1
1
  {
2
- "cli": "0.2.76",
3
- "docs-config": "0.2.76",
4
- "docs-theme": "0.2.76",
5
- "docs-transforms": "0.2.76"
2
+ "cli": "0.2.79",
3
+ "docs-config": "0.2.79",
4
+ "docs-theme": "0.2.79",
5
+ "docs-transforms": "0.2.79"
6
6
  }
@@ -4,7 +4,7 @@ description: Use when building destination-neutral visual explainer artifacts fr
4
4
  user-invocable: true
5
5
  allowed-tools: Read, Write, Edit, Bash, Grep, Glob, Agent, mcp__*
6
6
  metadata:
7
- version: 3.0.2
7
+ version: 3.0.3
8
8
  ---
9
9
 
10
10
  # Explainer Kit
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { createHash } from 'node:crypto';
4
+ import { realpathSync } from 'node:fs';
4
5
  import {
5
6
  lstat,
6
7
  mkdir,
@@ -20,8 +21,9 @@ import {
20
21
  isAbsolute,
21
22
  join,
22
23
  relative,
24
+ resolve,
23
25
  } from 'node:path';
24
- import { pathToFileURL } from 'node:url';
26
+ import { fileURLToPath } from 'node:url';
25
27
 
26
28
  import {
27
29
  harvestNumericTokens,
@@ -704,7 +706,29 @@ function bundleError(message) {
704
706
  return error;
705
707
  }
706
708
 
707
- if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
709
+ /**
710
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
711
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
712
+ * that exits 0 whenever the skill is reached through a symlinked install root,
713
+ * and canonicalizing only one side has the same effect under
714
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
715
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
716
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
717
+ * `false`; it never masks a direct run.
718
+ */
719
+ function isDirectInvocation(invokedPath) {
720
+ if (!invokedPath) return false;
721
+ try {
722
+ return (
723
+ realpathSync(fileURLToPath(import.meta.url)) ===
724
+ realpathSync(resolve(invokedPath))
725
+ );
726
+ } catch {
727
+ return false;
728
+ }
729
+ }
730
+
731
+ if (isDirectInvocation(process.argv[1])) {
708
732
  runBundle(process.argv.slice(2)).catch((error) => {
709
733
  console.error(error.message);
710
734
  process.exitCode = 1;
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
+ import { realpathSync } from 'node:fs';
4
5
  import { access, readFile, readdir, rename, writeFile } from 'node:fs/promises';
5
- import { join } from 'node:path';
6
- import { pathToFileURL } from 'node:url';
6
+ import { join, resolve } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
7
8
 
8
9
  import { validateContract } from './lib/contracts.mjs';
9
10
  import {
@@ -327,7 +328,29 @@ function recordError(code, message) {
327
328
  return error;
328
329
  }
329
330
 
330
- if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
331
+ /**
332
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
333
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
334
+ * that exits 0 whenever the skill is reached through a symlinked install root,
335
+ * and canonicalizing only one side has the same effect under
336
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
337
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
338
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
339
+ * `false`; it never masks a direct run.
340
+ */
341
+ function isDirectInvocation(invokedPath) {
342
+ if (!invokedPath) return false;
343
+ try {
344
+ return (
345
+ realpathSync(fileURLToPath(import.meta.url)) ===
346
+ realpathSync(resolve(invokedPath))
347
+ );
348
+ } catch {
349
+ return false;
350
+ }
351
+ }
352
+
353
+ if (isDirectInvocation(process.argv[1])) {
331
354
  runRecord(process.argv.slice(2)).catch((error) => {
332
355
  console.error(`${error.code}: ${error.message}`);
333
356
  process.exitCode = 1;
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { createHash } from 'node:crypto';
4
+ import { realpathSync } from 'node:fs';
4
5
  import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
5
- import { join } from 'node:path';
6
- import { pathToFileURL } from 'node:url';
6
+ import { join, resolve } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
7
8
 
8
9
  import { writeFailure } from './bundle.mjs';
9
10
  import {
@@ -640,7 +641,29 @@ function verifyError(code, message) {
640
641
  return error;
641
642
  }
642
643
 
643
- if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
644
+ /**
645
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
646
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
647
+ * that exits 0 whenever the skill is reached through a symlinked install root,
648
+ * and canonicalizing only one side has the same effect under
649
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
650
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
651
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
652
+ * `false`; it never masks a direct run.
653
+ */
654
+ function isDirectInvocation(invokedPath) {
655
+ if (!invokedPath) return false;
656
+ try {
657
+ return (
658
+ realpathSync(fileURLToPath(import.meta.url)) ===
659
+ realpathSync(resolve(invokedPath))
660
+ );
661
+ } catch {
662
+ return false;
663
+ }
664
+ }
665
+
666
+ if (isDirectInvocation(process.argv[1])) {
644
667
  runVerify(process.argv.slice(2)).catch((error) => {
645
668
  console.error(`${error.code ?? 'verify-error'}: ${error.message}`);
646
669
  process.exitCode = 1;
@@ -5,7 +5,7 @@ disable-model-invocation: false
5
5
  user-invocable: true
6
6
  allowed-tools: Read, Write, Edit, Bash, Grep, Glob, AskUserQuestion
7
7
  metadata:
8
- version: 1.0.9
8
+ version: 1.0.10
9
9
  ---
10
10
 
11
11
  # OAT Explainer Kit
@@ -1,4 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { realpathSync } from 'node:fs';
2
3
  import { lstat, readFile, realpath } from 'node:fs/promises';
3
4
  import {
4
5
  basename,
@@ -9,7 +10,7 @@ import {
9
10
  resolve,
10
11
  sep,
11
12
  } from 'node:path';
12
- import { pathToFileURL } from 'node:url';
13
+ import { fileURLToPath } from 'node:url';
13
14
 
14
15
  import { validateContract } from '../../explainer-kit/scripts/lib/contracts.mjs';
15
16
  import { isFlowFailureStage } from '../../explainer-kit/scripts/lib/failure.mjs';
@@ -324,10 +325,29 @@ function recapOutcomeError(message) {
324
325
  return error;
325
326
  }
326
327
 
327
- if (
328
- process.argv[1] &&
329
- import.meta.url === pathToFileURL(process.argv[1]).href
330
- ) {
328
+ /**
329
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
330
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
331
+ * that exits 0 whenever the skill is reached through a symlinked install root,
332
+ * and canonicalizing only one side has the same effect under
333
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
334
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
335
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
336
+ * `false`; it never masks a direct run.
337
+ */
338
+ function isDirectInvocation(invokedPath) {
339
+ if (!invokedPath) return false;
340
+ try {
341
+ return (
342
+ realpathSync(fileURLToPath(import.meta.url)) ===
343
+ realpathSync(resolve(invokedPath))
344
+ );
345
+ } catch {
346
+ return false;
347
+ }
348
+ }
349
+
350
+ if (isDirectInvocation(process.argv[1])) {
331
351
  main(process.argv.slice(2))
332
352
  .then((result) => {
333
353
  process.stdout.write(`${JSON.stringify(result)}\n`);
@@ -5,7 +5,7 @@ disable-model-invocation: true
5
5
  user-invocable: true
6
6
  allowed-tools: Read, Write, Bash, AskUserQuestion
7
7
  metadata:
8
- version: 1.7.12
8
+ version: 1.7.13
9
9
  ---
10
10
 
11
11
  # Complete Project
@@ -1,6 +1,7 @@
1
+ import { realpathSync } from 'node:fs';
1
2
  import { readdir, realpath } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import { pathToFileURL } from 'node:url';
3
+ import { join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
4
5
 
5
6
  import { readPersistedIntent } from '../../oat-explainer-kit/scripts/persist-intent.mjs';
6
7
 
@@ -110,10 +111,29 @@ async function main(argv) {
110
111
  return consumePersistedRecapIntent({ projectPath: argv[0] });
111
112
  }
112
113
 
113
- if (
114
- process.argv[1] &&
115
- import.meta.url === pathToFileURL(process.argv[1]).href
116
- ) {
114
+ /**
115
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
116
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
117
+ * that exits 0 whenever the skill is reached through a symlinked install root,
118
+ * and canonicalizing only one side has the same effect under
119
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
120
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
121
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
122
+ * `false`; it never masks a direct run.
123
+ */
124
+ function isDirectInvocation(invokedPath) {
125
+ if (!invokedPath) return false;
126
+ try {
127
+ return (
128
+ realpathSync(fileURLToPath(import.meta.url)) ===
129
+ realpathSync(resolve(invokedPath))
130
+ );
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+
136
+ if (isDirectInvocation(process.argv[1])) {
117
137
  main(process.argv.slice(2))
118
138
  .then((result) => process.stdout.write(`${JSON.stringify(result)}\n`))
119
139
  .catch((error) => {
@@ -1,7 +1,8 @@
1
1
  import { execFile as execFileCallback } from 'node:child_process';
2
+ import { realpathSync } from 'node:fs';
2
3
  import { readFile } from 'node:fs/promises';
3
- import { isAbsolute, posix, relative, sep } from 'node:path';
4
- import { pathToFileURL } from 'node:url';
4
+ import { isAbsolute, posix, relative, sep, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
5
6
  import { promisify } from 'node:util';
6
7
 
7
8
  import { validateSyncedArchiveTerminalReport } from './finalize-synced-archive.mjs';
@@ -261,10 +262,29 @@ async function main(argv) {
261
262
  });
262
263
  }
263
264
 
264
- if (
265
- process.argv[1] &&
266
- import.meta.url === pathToFileURL(process.argv[1]).href
267
- ) {
265
+ /**
266
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
267
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
268
+ * that exits 0 whenever the skill is reached through a symlinked install root,
269
+ * and canonicalizing only one side has the same effect under
270
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
271
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
272
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
273
+ * `false`; it never masks a direct run.
274
+ */
275
+ function isDirectInvocation(invokedPath) {
276
+ if (!invokedPath) return false;
277
+ try {
278
+ return (
279
+ realpathSync(fileURLToPath(import.meta.url)) ===
280
+ realpathSync(resolve(invokedPath))
281
+ );
282
+ } catch {
283
+ return false;
284
+ }
285
+ }
286
+
287
+ if (isDirectInvocation(process.argv[1])) {
268
288
  main(process.argv.slice(2))
269
289
  .then((result) => process.stdout.write(`${JSON.stringify(result)}\n`))
270
290
  .catch((error) => {
@@ -92,6 +92,10 @@ async function main(argv) {
92
92
  projectName,
93
93
  getArchiveReport: async () => {
94
94
  try {
95
+ // Stdin is read to EOF with no timeout. Every caller pipes the archive
96
+ // report in (`printf '%s\n' "$ARCHIVE_OUTPUT" | node ...`), so EOF
97
+ // always arrives; a timeout would turn a slow producer into a false
98
+ // failure, and a caller that leaves stdin open is a caller bug.
95
99
  return JSON.parse(readFileSync(0, 'utf8'));
96
100
  } catch (error) {
97
101
  throw finalizationError(
@@ -119,6 +123,9 @@ async function main(argv) {
119
123
  * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`. Either
120
124
  * way the caller reads "exited 0" as "verified" — the fail-open shape this
121
125
  * finalizer exists to prevent.
126
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
127
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
128
+ * `false`; it never masks a direct run.
122
129
  */
123
130
  function isDirectInvocation(invokedPath) {
124
131
  if (!invokedPath) return false;
@@ -1,4 +1,6 @@
1
- import { pathToFileURL } from 'node:url';
1
+ import { realpathSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
2
4
 
3
5
  const FULL_SHA = /^[0-9a-f]{40}$/;
4
6
  const PR_ARTIFACT = /^pr\/project-pr-[^/\s]+\.md$/;
@@ -217,10 +219,29 @@ function main(argv) {
217
219
  return parseCompletionRetryFields(argv[0]);
218
220
  }
219
221
 
220
- if (
221
- process.argv[1] &&
222
- import.meta.url === pathToFileURL(process.argv[1]).href
223
- ) {
222
+ /**
223
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
224
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
225
+ * that exits 0 whenever the skill is reached through a symlinked install root,
226
+ * and canonicalizing only one side has the same effect under
227
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
228
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
229
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
230
+ * `false`; it never masks a direct run.
231
+ */
232
+ function isDirectInvocation(invokedPath) {
233
+ if (!invokedPath) return false;
234
+ try {
235
+ return (
236
+ realpathSync(fileURLToPath(import.meta.url)) ===
237
+ realpathSync(resolve(invokedPath))
238
+ );
239
+ } catch {
240
+ return false;
241
+ }
242
+ }
243
+
244
+ if (isDirectInvocation(process.argv[1])) {
224
245
  try {
225
246
  process.stdout.write(`${main(process.argv.slice(2))}\n`);
226
247
  } catch (error) {
@@ -1,4 +1,6 @@
1
- import { pathToFileURL } from 'node:url';
1
+ import { realpathSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
2
4
 
3
5
  function fieldsError(message) {
4
6
  const error = new Error(message);
@@ -92,10 +94,29 @@ function main(argv) {
92
94
  return formatShellAssignments(parseSyncedArchiveResumeFields(result));
93
95
  }
94
96
 
95
- if (
96
- process.argv[1] &&
97
- import.meta.url === pathToFileURL(process.argv[1]).href
98
- ) {
97
+ /**
98
+ * Direct invocation, compared as canonical paths on both sides. Comparing a raw
99
+ * `process.argv[1]` against `import.meta.url` makes this script a silent no-op
100
+ * that exits 0 whenever the skill is reached through a symlinked install root,
101
+ * and canonicalizing only one side has the same effect under
102
+ * `--preserve-symlinks-main`, which keeps the link in `import.meta.url`.
103
+ * A path that cannot be canonicalized is not a module Node loaded as the entry
104
+ * point, so a thrown `realpathSync` means "not invoked directly" and returns
105
+ * `false`; it never masks a direct run.
106
+ */
107
+ function isDirectInvocation(invokedPath) {
108
+ if (!invokedPath) return false;
109
+ try {
110
+ return (
111
+ realpathSync(fileURLToPath(import.meta.url)) ===
112
+ realpathSync(resolve(invokedPath))
113
+ );
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+
119
+ if (isDirectInvocation(process.argv[1])) {
99
120
  try {
100
121
  process.stdout.write(`${main(process.argv.slice(2))}\n`);
101
122
  } catch (error) {