@sabaiway/agent-workflow-memory 4.5.0 → 4.5.2
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.
- package/CHANGELOG.md +46 -0
- package/SKILL.md +4 -4
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/scripts/archive-changelog.mjs +14 -3
- package/references/scripts/archive-decisions.mjs +14 -3
- package/references/scripts/archive-issues.mjs +14 -3
- package/references/scripts/check-docs-size.mjs +14 -3
- package/references/scripts/migrate-gates.mjs +13 -2
- package/references/templates/agent_rules.md +4 -5
- package/scripts/stamp-takeover.mjs +14 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,52 @@ All notable changes to the memory substrate. Versions are this **package's** npm
|
|
|
4
4
|
they are distinct from the **deployment-lineage** stamp written into a project's
|
|
5
5
|
`docs/ai/.memory-version` (which tracks the shared `agent-workflow` lineage, head `3.0.0`).
|
|
6
6
|
|
|
7
|
+
## 4.5.2 — the boundary sentence names the plan SHAPE, not the retired vocabulary (AD-104; ships with engine 3.0.0 / kit 6.0.0)
|
|
8
|
+
|
|
9
|
+
**Wording only — no contract, no script, no gate changes.** This substrate records only *where* the
|
|
10
|
+
workflow methodology lives, never the methodology itself, and the words it used to point with were
|
|
11
|
+
the ones engine **3.0.0** retired. Nothing here refuses anything it used to accept, and nothing new
|
|
12
|
+
is offered, so this stays a PATCH while its two siblings take a MAJOR each.
|
|
13
|
+
|
|
14
|
+
- **`SKILL.md`** — the "knows nobody else in the family" boundary paragraph and the ownership table
|
|
15
|
+
say **plan shape** (goal and boundary, module ledger, verification) and **activity procedures**
|
|
16
|
+
where they used to say "plan → execute → review vocabulary" and "Plan→Phase→Step". The slots, the
|
|
17
|
+
owners and the composition-root contract are untouched.
|
|
18
|
+
- **`references/templates/agent_rules.md`** — the rendered lens fragment picks up the same
|
|
19
|
+
re-render the kit template gets: the two per-Step clauses are per-row, and §5's plan-file summary
|
|
20
|
+
says "shape, lifecycle" instead of "vocabulary, lifecycle". The session-continuity sentence went
|
|
21
|
+
with the heuristic the canon dropped.
|
|
22
|
+
|
|
23
|
+
## 4.5.1 — the deployed scripts decide direct-run by real path, so a symlinked entry point stops silently doing nothing (AD-102; ships with kit 5.11.1)
|
|
24
|
+
|
|
25
|
+
**A script invoked through a symlink ran nothing and exited 0.** The guard that decides "was I run
|
|
26
|
+
directly, or imported?" compared `import.meta.url` against `process.argv[1]` — a comparison that is
|
|
27
|
+
false whenever the entry point reached the script through a symlink. The script then took the
|
|
28
|
+
imported-as-a-module path, did no work, and reported success. The fix has been sitting in-repo,
|
|
29
|
+
unpublished, since the delegation series' own measurement came back FAIL and the fix was correctly
|
|
30
|
+
held back from a release it should not have justified (AD-101). It ships here as the plain bug fix it
|
|
31
|
+
is.
|
|
32
|
+
|
|
33
|
+
- **The six standalone scripts this package deploys inline a fail-closed realpath guard.** A deployed
|
|
34
|
+
script cannot reach the kit's shared direct-run leaf, so one lexical line becomes an 11-line IIFE
|
|
35
|
+
that resolves both sides through `realpath` and refuses to guess when it cannot: `references/scripts/`
|
|
36
|
+
`archive-changelog.mjs` · `archive-decisions.mjs` · `archive-issues.mjs` · `check-docs-size.mjs` ·
|
|
37
|
+
`migrate-gates.mjs`, plus `scripts/stamp-takeover.mjs`.
|
|
38
|
+
- **No behaviour changes for a script invoked by its real path** — which is why this is a PATCH. What
|
|
39
|
+
changes is that invoking one through a symlink now does what you asked instead of nothing.
|
|
40
|
+
|
|
41
|
+
Recorded size effect (reason: a standalone deployed script cannot reach the kit's shared direct-run
|
|
42
|
+
leaf, so it inlines the realpath guard — one lexical line becomes an 11-line fail-closed IIFE):
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
agent-workflow-memory/references/scripts/archive-changelog.mjs: lines 546 -> 557 (raise)
|
|
46
|
+
agent-workflow-memory/references/scripts/archive-decisions.mjs: lines 1199 -> 1210 (raise)
|
|
47
|
+
agent-workflow-memory/references/scripts/archive-issues.mjs: lines 415 -> 426 (raise)
|
|
48
|
+
agent-workflow-memory/references/scripts/check-docs-size.mjs: lines 580 -> 591 (raise)
|
|
49
|
+
agent-workflow-memory/references/scripts/migrate-gates.mjs: lines 722 -> 733 (raise)
|
|
50
|
+
agent-workflow-memory: aggregate lines 10907 -> 10962 -> 10974 (raise)
|
|
51
|
+
```
|
|
52
|
+
|
|
7
53
|
## 4.5.0 — the closing state block gets a canon rule, and its slot labels are declared English (AD-098; ships with kit 5.10.0)
|
|
8
54
|
|
|
9
55
|
**Three slots that answer three different questions, or one restatement written three times.** The
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: agent-workflow-memory
|
|
|
3
3
|
description: Deploy or upgrade a portable AI-agent memory substrate in any project — an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) and a structured `docs/ai/` context store with cap/archive/index enforcement. Use when the user wants to bootstrap `docs/ai/`, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-memory` / `/agent-workflow-memory upgrade`. Triggers on "set up the memory system", "deploy the AI memory here", "bootstrap docs/ai", "upgrade the memory substrate". This is the substrate only — the workflow methodology (plan→execute→review, queue, Cleanup) is owned elsewhere and injected into AGENTS.md by the family composition root.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
metadata:
|
|
6
|
-
version: '4.5.
|
|
6
|
+
version: '4.5.2'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-memory
|
|
@@ -19,8 +19,8 @@ protocols, frontmatter caps, one-file-per-ADR archive, index-freshness gate. Enf
|
|
|
19
19
|
manually).
|
|
20
20
|
|
|
21
21
|
This skill is the **memory layer** of the `agent-workflow` family. It **knows nobody else** in
|
|
22
|
-
the family. In particular it does **not** own the **workflow methodology** (plan
|
|
23
|
-
|
|
22
|
+
the family. In particular it does **not** own the **workflow methodology** (the plan shape,
|
|
23
|
+
lifecycle, `docs/plans/queue.md`, mandatory Cleanup, the activity procedures), **the
|
|
24
24
|
orchestration recipes, or the autonomy-policy contract** — those are injected into **three delimited
|
|
25
25
|
pointer slots** in `AGENTS.md` (`workflow:methodology` + `workflow:orchestration` +
|
|
26
26
|
`workflow:autonomy`) by the family **composition root**, never by this
|
|
@@ -46,7 +46,7 @@ pointers are the **three empty slots** the composition root fills — never auth
|
|
|
46
46
|
| `docs/ai/` files, frontmatter caps, one-file-per-ADR archive, index-freshness gate | **memory** | `docs/ai/*` + scripts + hook |
|
|
47
47
|
| Visibility / conversational-language / agent-attribution contracts | **memory** | the three `AGENTS.md` blocks |
|
|
48
48
|
| Deployment-lineage stamp | **memory** | `docs/ai/.memory-version` |
|
|
49
|
-
| Plan
|
|
49
|
+
| Plan shape (goal and boundary, module ledger, verification), lifecycle, `queue.md`, mandatory Cleanup | **methodology** (not this skill) | the empty `workflow:methodology` slot — filled by the composition root |
|
|
50
50
|
| Orchestration recipes (Solo / Reviewed / Council / Delegated) | **methodology engine** (not this skill) | the empty `workflow:orchestration` slot — filled by the composition root |
|
|
51
51
|
| Autonomy-policy read contract (`docs/ai/autonomy.json`) | **methodology engine** (not this skill) | the empty `workflow:autonomy` slot — filled by the composition root |
|
|
52
52
|
| Per-project recipe **CONFIG** (which recipe each activity/slot uses) | **memory** seeds an *editable default* | `docs/ai/orchestration.json` (agent-writable via the composition root's `set-recipe` writer, or hand-edited; the recipe **canon** + the slot **vocabulary** live in the engine / composition root, never here) |
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-memory",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.2",
|
|
4
4
|
"description": "Portable, cross-agent memory substrate for AI coding agents — an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement, deployable standalone or as part of the agent-workflow family. The memory layer of the agent-workflow family.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
// --warm-days=N (default 30)
|
|
33
33
|
// --today=YYYY-MM-DD (default today UTC) — useful for tests / reproducible runs
|
|
34
34
|
|
|
35
|
-
import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync } from 'node:fs';
|
|
35
|
+
import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, realpathSync } from 'node:fs';
|
|
36
36
|
import { dirname, resolve, basename } from 'node:path';
|
|
37
|
-
import { fileURLToPath
|
|
37
|
+
import { fileURLToPath } from 'node:url';
|
|
38
38
|
import { tokenizeMarkdown, findParagraphBreak, fail } from './markdown-blocks.mjs';
|
|
39
39
|
|
|
40
40
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -542,5 +542,16 @@ export const runCli = (argv, deps = {}) => {
|
|
|
542
542
|
}
|
|
543
543
|
};
|
|
544
544
|
|
|
545
|
-
|
|
545
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
546
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
547
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
548
|
+
const isDirectRun = (() => {
|
|
549
|
+
const invoked = process.argv[1];
|
|
550
|
+
if (!invoked) return false;
|
|
551
|
+
try {
|
|
552
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
553
|
+
} catch {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
})();
|
|
546
557
|
if (isDirectRun) process.exitCode = runCli(process.argv.slice(2));
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
//
|
|
76
76
|
// Dependency-free, Node >= 22. Deployed into a consumer's scripts/ like its siblings.
|
|
77
77
|
|
|
78
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
78
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, realpathSync } from 'node:fs';
|
|
79
79
|
import { dirname, resolve, join, posix } from 'node:path';
|
|
80
|
-
import { fileURLToPath
|
|
80
|
+
import { fileURLToPath } from 'node:url';
|
|
81
81
|
import { spawnSync } from 'node:child_process';
|
|
82
82
|
import { createHash } from 'node:crypto';
|
|
83
83
|
import { tmpdir } from 'node:os';
|
|
@@ -1195,5 +1195,16 @@ export const runCli = (argv, deps = {}) => {
|
|
|
1195
1195
|
}
|
|
1196
1196
|
};
|
|
1197
1197
|
|
|
1198
|
-
|
|
1198
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
1199
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
1200
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
1201
|
+
const isDirectRun = (() => {
|
|
1202
|
+
const invoked = process.argv[1];
|
|
1203
|
+
if (!invoked) return false;
|
|
1204
|
+
try {
|
|
1205
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
1206
|
+
} catch {
|
|
1207
|
+
return false;
|
|
1208
|
+
}
|
|
1209
|
+
})();
|
|
1199
1210
|
if (isDirectRun) process.exitCode = runCli(process.argv.slice(2));
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
// --cutoff-days=N (default 14)
|
|
32
32
|
// --today=YYYY-MM-DD (default UTC today)
|
|
33
33
|
|
|
34
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
34
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, realpathSync } from 'node:fs';
|
|
35
35
|
import { dirname, resolve, basename } from 'node:path';
|
|
36
|
-
import { fileURLToPath
|
|
36
|
+
import { fileURLToPath } from 'node:url';
|
|
37
37
|
import { tokenizeMarkdown, fail } from './markdown-blocks.mjs';
|
|
38
38
|
|
|
39
39
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -411,5 +411,16 @@ export const runCli = (argv, deps = {}) => {
|
|
|
411
411
|
}
|
|
412
412
|
};
|
|
413
413
|
|
|
414
|
-
|
|
414
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
415
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
416
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
417
|
+
const isDirectRun = (() => {
|
|
418
|
+
const invoked = process.argv[1];
|
|
419
|
+
if (!invoked) return false;
|
|
420
|
+
try {
|
|
421
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
422
|
+
} catch {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
})();
|
|
415
426
|
if (isDirectRun) process.exitCode = runCli(process.argv.slice(2));
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
// --quiet print only failures (and final summary)
|
|
26
26
|
|
|
27
27
|
import { readFile, writeFile, readdir, stat, rename, rm } from 'node:fs/promises';
|
|
28
|
-
import { existsSync, lstatSync } from 'node:fs';
|
|
28
|
+
import { existsSync, lstatSync, realpathSync } from 'node:fs';
|
|
29
29
|
import { dirname, resolve, relative, join, basename, sep } from 'node:path';
|
|
30
|
-
import { fileURLToPath
|
|
30
|
+
import { fileURLToPath } from 'node:url';
|
|
31
31
|
import { randomBytes } from 'node:crypto';
|
|
32
32
|
|
|
33
33
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -571,7 +571,18 @@ export const runCli = async (argv, deps = {}) => {
|
|
|
571
571
|
return result(errorCount > 0 && !flags.report ? 1 : 0);
|
|
572
572
|
};
|
|
573
573
|
|
|
574
|
-
|
|
574
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
575
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
576
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
577
|
+
const isDirectRun = (() => {
|
|
578
|
+
const invoked = process.argv[1];
|
|
579
|
+
if (!invoked) return false;
|
|
580
|
+
try {
|
|
581
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
582
|
+
} catch {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
})();
|
|
575
586
|
if (isDirectRun) {
|
|
576
587
|
const { code, stdout, stderr } = await runCli(process.argv.slice(2));
|
|
577
588
|
if (stdout) process.stdout.write(stdout);
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
import { existsSync, lstatSync, readFileSync, writeFileSync, renameSync, unlinkSync, realpathSync } from 'node:fs';
|
|
30
30
|
import { join, resolve, isAbsolute } from 'node:path';
|
|
31
|
-
import {
|
|
31
|
+
import { fileURLToPath } from 'node:url';
|
|
32
32
|
import { randomBytes } from 'node:crypto';
|
|
33
33
|
import { spawnSync } from 'node:child_process';
|
|
34
34
|
|
|
@@ -718,5 +718,16 @@ export const main = (argv = process.argv.slice(2), io = {}) => {
|
|
|
718
718
|
}
|
|
719
719
|
};
|
|
720
720
|
|
|
721
|
-
|
|
721
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
722
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
723
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
724
|
+
const isDirectRun = (() => {
|
|
725
|
+
const invoked = process.argv[1];
|
|
726
|
+
if (!invoked) return false;
|
|
727
|
+
try {
|
|
728
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
729
|
+
} catch {
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
})();
|
|
722
733
|
if (isDirectRun) process.exitCode = main();
|
|
@@ -79,11 +79,11 @@ Apply this as part of §2 before any user-facing summary:
|
|
|
79
79
|
Apply these when authoring a plan, reviewing, folding a finding, or editing code — the layer read **before any code change**. (Full canon: the project's planning / workflow-methodology + orchestration canon. This section is rendered from that canon and refreshed on upgrade; a custom edit is preserved verbatim, but flagged.)
|
|
80
80
|
- **Fold by code, not prose.** Before folding a code-touching finding into a plan or change, read the cited `file:line` and cite it — a prose fold drifts from the code and seeds the next bug.
|
|
81
81
|
- **Right altitude.** Pin intent + invariants + acceptance criteria (named tests); leave fine code-mechanics to Execute, where prose cannot diverge from reality.
|
|
82
|
-
- **No code-mechanics in the plan.** A
|
|
82
|
+
- **No code-mechanics in the plan.** A ledger row carries its path and anchor, and Verification carries the exact commands (the plan-shape canon) — checked syntax: the plan's own Verification runs them against an explicit expected outcome or gate; the only other syntax a plan may carry is a literal fixture/schema fragment a named test copies or validates. Un-run, logic-bearing syntax — control-flow, a regex, a glob, a grammar, an algorithm body, a mini-DSL — never lives in plan prose, however plausible or shell-verified it looks: a fold or draft that wants one is the trigger to write the test instead.
|
|
83
83
|
- **Test-as-spec.** Fold a code-touching finding into a red→green TEST, not a prose paragraph — the gate is the only deterministic checker; a paragraph cannot self-check.
|
|
84
84
|
- **Characterize-first.** Before editing UNCOVERED code, pin its current behavior in a green test, then edit — any unintended change goes red. Never edit what has no checker; first give it one. Keep edits atomic/reversible; prefer SUBTRACTIVE folds.
|
|
85
85
|
- **Fold minimally — prose has no checker.** An ephemeral, gitignored plan is prose with no executable checker; fold **minimally, in ONE place** and run a **self-consistency** read across the plan before every re-review — a fold that drifts several prose spots is what turns a 2-round review into churn.
|
|
86
|
-
- **Heavy review at the diff.** Plan-review settles architecture only (≤2 rounds, stop at the pre-existing→fold-induced crossover); the exhaustive per-
|
|
86
|
+
- **Heavy review at the diff.** Plan-review settles architecture only (≤2 rounds, stop at the pre-existing→fold-induced crossover); the exhaustive per-row review runs against real compiling code + the full suite, where a regression fails a gate immediately. **Backend divergence** (one backend grounded-ships while another keeps revising mechanics) IS that crossover — resolve at altitude, don't exhaust the strictest backend; route an all-mechanics/CI or prose-only artifact to a **thin plan + diff-review**.
|
|
87
87
|
- **Convergence bar.** A review loop is CLEAN only when one round returns **0 blockers + 0 majors** from EVERY backend the recipe names (nits + a ship verdict is the stop). Folding ≠ convergence — re-review after folding.
|
|
88
88
|
- **Per-round emission.** Every review round emits **{round N · finding-origin tally · per-backend verdict}** so the crossover is a computed, visible signal, not a remembered rule.
|
|
89
89
|
- **Recipe fidelity.** Council runs every backend the recipe names, **every round**; silently dropping a ready backend for quota/convenience is a forbidden downgrade — an unavailable backend is a LOUD, stated degrade, never a quiet drop.
|
|
@@ -98,8 +98,7 @@ Split a complex task across sessions for **focus and review hygiene**, not becau
|
|
|
98
98
|
|
|
99
99
|
- **Split (separate sessions)** when the *change* is large enough to deserve an isolated review checkpoint: creates/deletes a source or test file, touches several files, alters a dependency / core config / data model / routes, or needs new E2E tests. These are review-hygiene triggers, independent of context size.
|
|
100
100
|
- **Run inline** for small, self-contained work: a few files, no new files, no dependency/schema/route change, light discussion (typos, tweaks, single-line fixes, test additions).
|
|
101
|
-
- **Context size is a soft, secondary signal** — split only when the working context is genuinely large *relative to the window* or you notice degraded recall, never at a fixed token count.
|
|
102
|
-
|
|
101
|
+
- **Context size is a soft, secondary signal** — split only when the working context is genuinely large *relative to the window* or you notice degraded recall, never at a fixed token count.
|
|
103
102
|
---
|
|
104
103
|
|
|
105
104
|
## 4. User Interaction
|
|
@@ -111,4 +110,4 @@ Split a complex task across sessions for **focus and review hygiene**, not becau
|
|
|
111
110
|
|
|
112
111
|
## 5. Planning Workflow
|
|
113
112
|
|
|
114
|
-
Plan-file rules —
|
|
113
|
+
Plan-file rules — shape, lifecycle (`docs/plans/<slug>.md`, gitignored, never committed), the mandatory final **Phase: Cleanup**, the `docs/plans/queue.md` series-index, "all work in plans" — are **workflow methodology**, owned by the project's planning skill, not by this memory substrate. The methodology block in `AGENTS.md` (between the `workflow:methodology` markers) points to it; the planning skill is the single source of truth and overrides the generic `writing-plans` skill. This substrate records only *where* the methodology lives, never the methodology itself.
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
// The Markdown twin `migrations/legacy-stamp-takeover.md` documents the same table as the
|
|
16
16
|
// no-Node manual fallback. Dependency-free, Node >= 22.
|
|
17
17
|
|
|
18
|
+
import { realpathSync } from 'node:fs';
|
|
18
19
|
import { readFile, writeFile, rename, unlink } from 'node:fs/promises';
|
|
19
20
|
import { dirname, basename, join, resolve } from 'node:path';
|
|
20
21
|
import { randomBytes } from 'node:crypto';
|
|
21
|
-
import {
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
22
23
|
|
|
23
24
|
// The shared agent-workflow deployment-lineage head. Bumped only when a project-migration
|
|
24
25
|
// changes the deployed docs/ai structure — NOT on a packaging-only release.
|
|
@@ -177,5 +178,16 @@ const main = async (argv) => {
|
|
|
177
178
|
if (decision.status === 'stop') process.exit(1);
|
|
178
179
|
};
|
|
179
180
|
|
|
180
|
-
|
|
181
|
+
// Run main() only when executed directly, never on import. Compare by REAL path: an entry point
|
|
182
|
+
// reached through a symlink resolves to its target, so a raw string compare reads the two as
|
|
183
|
+
// different and the CLI never runs. realpathSync collapses the link so both sides match.
|
|
184
|
+
const isDirectRun = (() => {
|
|
185
|
+
const invoked = process.argv[1];
|
|
186
|
+
if (!invoked) return false;
|
|
187
|
+
try {
|
|
188
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
189
|
+
} catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
})();
|
|
181
193
|
if (isDirectRun) await main(process.argv.slice(2));
|