@kontourai/flow-agents 1.4.0 → 2.0.1
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/.github/CODEOWNERS +29 -0
- package/.github/actions/trust-verify/action.yml +145 -0
- package/.github/workflows/ci.yml +11 -4
- package/.github/workflows/kit-gates-demo.yml +2 -2
- package/.github/workflows/publish-npm.yml +10 -2
- package/.github/workflows/release-please.yml +1 -1
- package/.github/workflows/runtime-compat.yml +1 -1
- package/.github/workflows/trust-reconcile.yml +113 -0
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +103 -0
- package/CONTRIBUTING.md +4 -4
- package/README.md +1 -0
- package/agents/tool-planner.json +1 -1
- package/build/src/cli/init.js +242 -20
- package/build/src/cli/validate-workflow-artifacts.js +19 -2
- package/build/src/cli/verify.d.ts +1 -0
- package/build/src/cli/verify.js +90 -0
- package/build/src/cli/workflow-sidecar.d.ts +316 -8
- package/build/src/cli/workflow-sidecar.js +1996 -91
- package/build/src/cli.js +2 -3
- package/build/src/lib/flow-resolver.d.ts +111 -0
- package/build/src/lib/flow-resolver.js +308 -0
- package/build/src/tools/build-universal-bundles.js +34 -22
- package/build/src/tools/generate-context-map.js +3 -16
- package/build/src/tools/validate-source-tree.d.ts +1 -1
- package/build/src/tools/validate-source-tree.js +42 -162
- package/context/contracts/artifact-contract.md +10 -0
- package/context/contracts/delivery-contract.md +1 -0
- package/context/contracts/review-contract.md +1 -0
- package/context/contracts/verification-contract.md +2 -0
- package/context/gate-awareness.md +39 -0
- package/context/scripts/hooks/stop-goal-fit.js +632 -70
- package/docs/adr/0001-flow-agents-consumes-flow.md +1 -1
- package/docs/adr/0002-flow-kits-as-extension-unit.md +1 -1
- package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
- package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
- package/docs/adr/0007-skill-audit.md +1 -1
- package/docs/adr/0009-canonical-hook-core-kit-boundary.md +95 -0
- package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +139 -0
- package/docs/adr/0011-mcp-posture.md +100 -0
- package/docs/adr/0012-agent-coordination-as-liveness-claims.md +119 -0
- package/docs/adr/0013-context-lifecycle.md +151 -0
- package/docs/adr/0014-core-vs-domain-kit-boundary.md +143 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +120 -0
- package/docs/adr/0016-three-hard-boundary-model.md +71 -0
- package/docs/adr/0017-anti-gaming-trust-security-model.md +155 -0
- package/docs/agent-system-guidebook.md +5 -12
- package/docs/context-map.md +4 -10
- package/docs/index.md +3 -2
- package/docs/integrations/framework-adapter.md +19 -6
- package/docs/integrations/index.md +2 -2
- package/docs/north-star.md +4 -4
- package/docs/operating-layers.md +3 -3
- package/docs/plans/adr-0010-phase2-gate-recompute.md +55 -0
- package/docs/repository-structure.md +2 -2
- package/docs/skills-map.md +1 -0
- package/docs/spec/runtime-hook-surface.md +62 -9
- package/docs/standards-register.md +3 -3
- package/docs/survey-utterance-check.md +1 -1
- package/docs/trust-anchor-adoption.md +197 -0
- package/docs/verifiable-trust.md +95 -0
- package/docs/veritas-integration.md +2 -2
- package/docs/workflow-usage-guide.md +69 -0
- package/evals/acceptance/DEMO-false-completion.md +144 -0
- package/evals/acceptance/demo-cast.sh +92 -0
- package/evals/acceptance/demo-false-completion.sh +72 -0
- package/evals/acceptance/demo-real-evidence.sh +104 -0
- package/evals/acceptance/demo.tape +29 -0
- package/evals/acceptance/prove-capture-teeth-declared.sh +335 -0
- package/evals/acceptance/prove-capture-teeth.sh +114 -0
- package/evals/acceptance/prove-teeth.sh +105 -0
- package/evals/ci/antigaming-suite.sh +55 -0
- package/evals/ci/run-baseline.sh +2 -0
- package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/kit.json +20 -0
- package/evals/fixtures/flow-kit-repository/valid-unknown-extension/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/valid-unknown-extension/kit.json +18 -0
- package/evals/integration/test_builder_step_producers.sh +379 -0
- package/evals/integration/test_bundle_install.sh +35 -71
- package/evals/integration/test_bundle_lifecycle.sh +39 -2
- package/evals/integration/test_captured_fail_reconciliation.sh +820 -0
- package/evals/integration/test_checkpoint_signing.sh +489 -0
- package/evals/integration/test_claim_lookup.sh +352 -0
- package/evals/integration/test_command_log_fork_classification.sh +134 -0
- package/evals/integration/test_command_log_integrity.sh +275 -0
- package/evals/integration/test_context_map.sh +0 -2
- package/evals/integration/test_dual_emit_flow_step.sh +278 -0
- package/evals/integration/test_enforcer_expects_driven.sh +281 -0
- package/evals/integration/test_evidence_capture_hook.sh +185 -0
- package/evals/integration/test_flow_kit_repository.sh +2 -0
- package/evals/integration/test_flowdef_session_activation.sh +273 -0
- package/evals/integration/test_flowdef_session_history_preservation.sh +250 -0
- package/evals/integration/test_gate_bypass_chain.sh +448 -0
- package/evals/integration/test_gate_lockdown.sh +1137 -0
- package/evals/integration/test_gate_review_inquiry_records.sh +399 -0
- package/evals/integration/test_goal_fit_escape_hatch.sh +73 -0
- package/evals/integration/test_goal_fit_hook.sh +69 -4
- package/evals/integration/test_goal_fit_rederive.sh +263 -0
- package/evals/integration/test_install_merge.sh +1176 -0
- package/evals/integration/test_kit_identity_trust.sh +393 -0
- package/evals/integration/test_mint_attestation.sh +373 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +365 -0
- package/evals/integration/test_publish_delivery.sh +269 -0
- package/evals/integration/test_reconcile_soundness.sh +528 -0
- package/evals/integration/test_resolvefirststep_security.sh +208 -0
- package/evals/integration/test_session_resume_roundtrip.sh +286 -0
- package/evals/integration/test_trust_checkpoint.sh +325 -0
- package/evals/integration/test_trust_reconcile.sh +293 -0
- package/evals/integration/test_verify_cli.sh +208 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +549 -34
- package/evals/lib/node.sh +0 -6
- package/evals/run.sh +47 -0
- package/evals/static/test_workflow_skills.sh +6 -13
- package/install.sh +0 -7
- package/integrations/strands-ts/README.md +25 -15
- package/integrations/veritas/flow-agents.adapter.json +1 -2
- package/kits/builder/flows/build.flow.json +59 -12
- package/kits/builder/kit.json +85 -15
- package/kits/builder/skills/continue-work/SKILL.md +116 -0
- package/kits/builder/skills/deliver/SKILL.md +36 -6
- package/kits/builder/skills/design-probe/SKILL.md +28 -0
- package/kits/builder/skills/execute-plan/SKILL.md +9 -1
- package/kits/builder/skills/gate-review/SKILL.md +234 -0
- package/kits/builder/skills/learning-review/SKILL.md +30 -0
- package/kits/builder/skills/pickup-probe/SKILL.md +29 -0
- package/kits/builder/skills/plan-work/SKILL.md +13 -1
- package/kits/builder/skills/pull-work/SKILL.md +19 -0
- package/kits/knowledge/adapters/default-store/index.js +38 -0
- package/kits/knowledge/adapters/flow-runner/index.js +1620 -0
- package/kits/knowledge/adapters/obsidian-store/index.js +36 -6
- package/kits/knowledge/docs/store-contract.md +314 -0
- package/kits/knowledge/evals/audit-freshness/suite.test.js +368 -0
- package/kits/knowledge/evals/canonicalize-category/suite.test.js +383 -0
- package/kits/knowledge/evals/contract-suite/suite.test.js +111 -0
- package/kits/knowledge/evals/detect-contradictions/suite.test.js +324 -0
- package/kits/knowledge/evals/entities/suite.test.js +40 -0
- package/kits/knowledge/evals/glossary-sync/suite.test.js +416 -0
- package/kits/knowledge/evals/hygiene-review/suite.test.js +396 -0
- package/kits/knowledge/evals/retirement/suite.test.js +145 -0
- package/kits/knowledge/flows/audit-freshness.flow.json +44 -0
- package/kits/knowledge/flows/canonicalize-category.flow.json +44 -0
- package/kits/knowledge/flows/detect-contradictions.flow.json +44 -0
- package/kits/knowledge/flows/glossary-sync.flow.json +61 -0
- package/kits/knowledge/flows/hygiene-review.flow.json +43 -0
- package/kits/knowledge/kit.json +51 -1
- package/package.json +6 -6
- package/packaging/conformance/README.md +10 -2
- package/packaging/conformance/fixtures/evidence-capture--allow-records-command.json +29 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +29 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +30 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-mode.json +23 -0
- package/packaging/conformance/fixtures/stop-goal-fit--off-mode.json +24 -0
- package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +5 -2
- package/packaging/conformance/fixtures/stop-goal-fit--warn-no-bundle.json +23 -0
- package/packaging/conformance/fixtures/workflow-steering--reground-active-prompt.json +30 -0
- package/packaging/conformance/fixtures/workflow-steering--reground-session-start.json +30 -0
- package/packaging/conformance/run-conformance.js +1 -1
- package/scripts/README.md +2 -1
- package/scripts/build-universal-bundles.js +0 -1
- package/scripts/ci/mint-attestation.js +221 -0
- package/scripts/ci/trust-reconcile.js +545 -0
- package/scripts/hooks/config-protection.js +423 -1
- package/scripts/hooks/evidence-capture.js +348 -0
- package/scripts/hooks/lib/liveness-read.js +113 -0
- package/scripts/hooks/run-hook.js +6 -1
- package/scripts/hooks/stop-goal-fit.js +1524 -79
- package/scripts/hooks/workflow-steering.js +135 -5
- package/scripts/install-codex-home.sh +39 -0
- package/scripts/install-merge.js +330 -0
- package/scripts/repair-command-log.js +115 -0
- package/src/cli/init.ts +218 -20
- package/src/cli/validate-workflow-artifacts.ts +18 -2
- package/src/cli/verify.ts +100 -0
- package/src/cli/workflow-sidecar.ts +2127 -84
- package/src/cli.ts +2 -3
- package/src/lib/flow-resolver.ts +369 -0
- package/src/tools/build-universal-bundles.ts +34 -21
- package/src/tools/generate-context-map.ts +3 -17
- package/src/tools/validate-source-tree.ts +44 -104
- package/build/src/tools/filter-installed-packs.d.ts +0 -2
- package/build/src/tools/filter-installed-packs.js +0 -135
- package/packaging/packs.json +0 -49
- package/scripts/filter-installed-packs.js +0 -2
- package/src/tools/filter-installed-packs.ts +0 -132
package/src/cli/init.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
4
5
|
import * as os from "node:os";
|
|
5
6
|
import * as path from "node:path";
|
|
6
7
|
import { createInterface } from "node:readline/promises";
|
|
@@ -16,13 +17,13 @@ type TelemetrySink = "local-files" | "local-kontour-console" | "kontour-hosted-c
|
|
|
16
17
|
type InitOptions = {
|
|
17
18
|
runtime: Runtime;
|
|
18
19
|
dest: string;
|
|
20
|
+
global?: boolean;
|
|
19
21
|
consoleUrl?: string;
|
|
20
22
|
consoleEndpoint?: string;
|
|
21
23
|
consoleTokenFile?: string;
|
|
22
24
|
consoleTokenValue?: string;
|
|
23
25
|
consoleTenant?: string;
|
|
24
26
|
telemetrySinks: TelemetrySink[];
|
|
25
|
-
packs?: string;
|
|
26
27
|
activateKits: boolean;
|
|
27
28
|
};
|
|
28
29
|
|
|
@@ -95,12 +96,19 @@ function usage(): void {
|
|
|
95
96
|
Options:
|
|
96
97
|
--runtime base|codex|claude-code|kiro|opencode|pi
|
|
97
98
|
--dest PATH
|
|
99
|
+
--global Target the runtime's global/user-level config path.
|
|
100
|
+
claude-code: merges FA hooks into ~/.claude/settings.json.
|
|
101
|
+
Honors FLOW_AGENTS_USER_CLAUDE_SETTINGS for test isolation.
|
|
102
|
+
opencode: merges opencode.json into ~/.config/opencode/opencode.json
|
|
103
|
+
(honors XDG_CONFIG_HOME; test isolation via FLOW_AGENTS_USER_OPENCODE_CONFIG).
|
|
104
|
+
codex: runs install-codex-home.sh into ~/.flow-agents/codex
|
|
105
|
+
(the isolated Codex HOME; hooks merged, not overwritten).
|
|
106
|
+
pi: NOT_VERIFIED (no documented global dir); warns and falls back to workspace default.
|
|
98
107
|
--telemetry-sink local-files|local-kontour-console|kontour-hosted-console|user-hosted-console
|
|
99
108
|
--console-url URL
|
|
100
109
|
--console-endpoint URL
|
|
101
110
|
--console-token-file PATH
|
|
102
111
|
--console-tenant ID
|
|
103
|
-
--packs LIST
|
|
104
112
|
--activate-kits
|
|
105
113
|
--yes, --headless
|
|
106
114
|
`);
|
|
@@ -169,6 +177,34 @@ function defaultDest(runtime: Runtime): string {
|
|
|
169
177
|
return process.cwd();
|
|
170
178
|
}
|
|
171
179
|
|
|
180
|
+
function globalDest(runtime: Runtime): string {
|
|
181
|
+
if (runtime === "claude-code") {
|
|
182
|
+
// Honor FLOW_AGENTS_USER_CLAUDE_SETTINGS for test isolation (same as checkScopeCollision).
|
|
183
|
+
const override = process.env["FLOW_AGENTS_USER_CLAUDE_SETTINGS"];
|
|
184
|
+
if (override) return path.dirname(override);
|
|
185
|
+
return path.join(os.homedir(), ".claude");
|
|
186
|
+
}
|
|
187
|
+
if (runtime === "opencode") {
|
|
188
|
+
// Honor FLOW_AGENTS_USER_OPENCODE_CONFIG (points to the opencode.json FILE) for test isolation,
|
|
189
|
+
// mirroring FLOW_AGENTS_USER_CLAUDE_SETTINGS for claude-code.
|
|
190
|
+
const override = process.env["FLOW_AGENTS_USER_OPENCODE_CONFIG"];
|
|
191
|
+
if (override) return path.dirname(override);
|
|
192
|
+
// Global opencode config: ~/.config/opencode/ (honor XDG_CONFIG_HOME when set, else ~/.config).
|
|
193
|
+
return path.join(process.env["XDG_CONFIG_HOME"] ?? path.join(os.homedir(), ".config"), "opencode");
|
|
194
|
+
}
|
|
195
|
+
if (runtime === "codex") {
|
|
196
|
+
// codex --global routes to the isolated Codex HOME at ~/.flow-agents/codex.
|
|
197
|
+
// This is the same path used by install-codex-home.sh; --dest overrides it for sandbox testing.
|
|
198
|
+
return path.join(os.homedir(), ".flow-agents", "codex");
|
|
199
|
+
}
|
|
200
|
+
if (runtime === "pi") {
|
|
201
|
+
// pi has no documented global config dir.
|
|
202
|
+
// NOT_VERIFIED: fall back to workspace default and warn caller.
|
|
203
|
+
return defaultDest(runtime);
|
|
204
|
+
}
|
|
205
|
+
return defaultDest(runtime);
|
|
206
|
+
}
|
|
207
|
+
|
|
172
208
|
function parseYesNo(value: string, fallback: boolean): boolean {
|
|
173
209
|
const normalized = value.trim().toLowerCase();
|
|
174
210
|
if (!normalized) return fallback;
|
|
@@ -184,7 +220,9 @@ async function interactiveOptions(argv: string[]): Promise<InitOptions> {
|
|
|
184
220
|
const runtimeDefault = normalizeRuntime(flagString(args.flags, "runtime")) ?? "base";
|
|
185
221
|
const runtimeAnswer = flagString(args.flags, "runtime") ?? await rl.question(`Runtime [${runtimeDefault}]: `);
|
|
186
222
|
const runtime = normalizeRuntime(runtimeAnswer.trim() || runtimeDefault) ?? runtimeDefault;
|
|
187
|
-
const
|
|
223
|
+
const isGlobal = flagBool(args.flags, "global");
|
|
224
|
+
const destBase = isGlobal ? globalDest(runtime) : defaultDest(runtime);
|
|
225
|
+
const destDefault = flagString(args.flags, "dest", destBase) ?? destBase;
|
|
188
226
|
const destAnswer = flagString(args.flags, "dest") ?? await rl.question(`Install destination [${destDefault}]: `);
|
|
189
227
|
const sinkDefault = telemetrySinksFromFlags(args.flags);
|
|
190
228
|
const sinkAnswer = flagList(args.flags, "telemetry-sink").length || flagList(args.flags, "telemetry-sinks").length
|
|
@@ -205,6 +243,7 @@ async function interactiveOptions(argv: string[]): Promise<InitOptions> {
|
|
|
205
243
|
: "no";
|
|
206
244
|
return {
|
|
207
245
|
runtime,
|
|
246
|
+
global: isGlobal,
|
|
208
247
|
dest: path.resolve(destAnswer.trim() || destDefault),
|
|
209
248
|
consoleUrl: consoleUrl?.trim() || undefined,
|
|
210
249
|
consoleEndpoint: consoleEndpoint?.trim() || undefined,
|
|
@@ -212,7 +251,6 @@ async function interactiveOptions(argv: string[]): Promise<InitOptions> {
|
|
|
212
251
|
consoleTokenValue: consoleTokenValue?.trim() || undefined,
|
|
213
252
|
consoleTenant: consoleTenant?.trim() || undefined,
|
|
214
253
|
telemetrySinks,
|
|
215
|
-
packs: flagString(args.flags, "packs"),
|
|
216
254
|
activateKits: runtime === "codex" && parseYesNo(activateAnswer, activateDefault),
|
|
217
255
|
};
|
|
218
256
|
} finally {
|
|
@@ -223,15 +261,17 @@ async function interactiveOptions(argv: string[]): Promise<InitOptions> {
|
|
|
223
261
|
function headlessOptions(argv: string[]): InitOptions {
|
|
224
262
|
const args = parseArgs(argv);
|
|
225
263
|
const runtime = normalizeRuntime(flagString(args.flags, "runtime")) ?? "base";
|
|
264
|
+
const isGlobal = flagBool(args.flags, "global");
|
|
265
|
+
const destBase = isGlobal ? globalDest(runtime) : defaultDest(runtime);
|
|
226
266
|
return {
|
|
227
267
|
runtime,
|
|
228
|
-
|
|
268
|
+
global: isGlobal,
|
|
269
|
+
dest: path.resolve(flagString(args.flags, "dest", destBase) ?? destBase),
|
|
229
270
|
consoleUrl: flagString(args.flags, "console-url"),
|
|
230
271
|
consoleEndpoint: flagString(args.flags, "console-endpoint") ?? flagString(args.flags, "console-endpoint-url"),
|
|
231
272
|
consoleTokenFile: flagString(args.flags, "console-token-file"),
|
|
232
273
|
consoleTenant: flagString(args.flags, "console-tenant") ?? flagString(args.flags, "console-tenant-id"),
|
|
233
274
|
telemetrySinks: telemetrySinksFromFlags(args.flags),
|
|
234
|
-
packs: flagString(args.flags, "packs"),
|
|
235
275
|
activateKits: runtime === "codex" && flagBool(args.flags, "activate-kits"),
|
|
236
276
|
};
|
|
237
277
|
}
|
|
@@ -262,7 +302,6 @@ function installBundle(bundle: string, options: InitOptions): number {
|
|
|
262
302
|
if (consoleTokenFile) args.push("--console-token-file", consoleTokenFile);
|
|
263
303
|
if (options.consoleTenant) args.push("--console-tenant", options.consoleTenant);
|
|
264
304
|
const env = { ...process.env };
|
|
265
|
-
if (options.packs) env.FLOW_AGENTS_PACKS = options.packs;
|
|
266
305
|
const result = spawnSync("bash", args, { cwd: bundle, env, encoding: "utf8", stdio: "inherit" });
|
|
267
306
|
if (tempTokenFile) fs.rmSync(path.dirname(tempTokenFile), { recursive: true, force: true });
|
|
268
307
|
if (result.error) {
|
|
@@ -302,7 +341,122 @@ export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
|
302
341
|
// There is no well-known user-level codex hooks file in our install paths,
|
|
303
342
|
// so no collision check is needed for codex.
|
|
304
343
|
if (options.runtime === "claude-code") {
|
|
305
|
-
|
|
344
|
+
// Skip collision check when --global is set: the user is intentionally
|
|
345
|
+
// targeting the global settings, so the collision they'd create is expected.
|
|
346
|
+
if (!options.global) checkScopeCollision();
|
|
347
|
+
}
|
|
348
|
+
// --global for claude-code: merge only into the global/user-level settings dir.
|
|
349
|
+
// This writes only the hook-wiring config (merge into ~/.claude/settings.json),
|
|
350
|
+
// not the full workspace bundle. The global settings dir is the claude config root,
|
|
351
|
+
// so the settings.json lives directly in dest (not dest/.claude/).
|
|
352
|
+
if (options.global && options.runtime === "claude-code") {
|
|
353
|
+
const bundle = ensureBundle(options.runtime);
|
|
354
|
+
// For --global, dest is ~/.claude/ (the global settings dir).
|
|
355
|
+
// dogfoodClaudeCode writes to dest/.claude/settings.json — but for global,
|
|
356
|
+
// the settings.json lives at dest/settings.json (dest IS ~/.claude/).
|
|
357
|
+
// We use a special global-merge path: merge directly into dest/settings.json.
|
|
358
|
+
const sourcePath = path.join(bundle, ".claude", "settings.json");
|
|
359
|
+
if (!fs.existsSync(sourcePath)) {
|
|
360
|
+
console.error(`flow-agents init: bundle settings missing: ${sourcePath}`);
|
|
361
|
+
return 1;
|
|
362
|
+
}
|
|
363
|
+
const managed = JSON.parse(fs.readFileSync(sourcePath, "utf8")) as Record<string, unknown>;
|
|
364
|
+
// Remove permissive defaults (not appropriate for global user settings).
|
|
365
|
+
delete managed["permissions"];
|
|
366
|
+
delete managed["skipDangerousModePermissionPrompt"];
|
|
367
|
+
fs.mkdirSync(options.dest, { recursive: true });
|
|
368
|
+
const destSettingsPath = path.join(options.dest, "settings.json");
|
|
369
|
+
const installMergePath = path.join(root, "scripts", "install-merge.js");
|
|
370
|
+
const _require = createRequire(import.meta.url);
|
|
371
|
+
const { mergeSettings } = _require(installMergePath) as { mergeSettings: (a: Record<string, unknown>, b: Record<string, unknown>) => Record<string, unknown> };
|
|
372
|
+
let existing: Record<string, unknown> = {};
|
|
373
|
+
if (fs.existsSync(destSettingsPath)) {
|
|
374
|
+
try { existing = JSON.parse(fs.readFileSync(destSettingsPath, "utf8")) as Record<string, unknown>; } catch { existing = {}; }
|
|
375
|
+
}
|
|
376
|
+
const merged = mergeSettings(existing, managed);
|
|
377
|
+
const tmp = `${destSettingsPath}.tmp.${process.pid}`;
|
|
378
|
+
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
379
|
+
fs.renameSync(tmp, destSettingsPath);
|
|
380
|
+
// Write version stamp.
|
|
381
|
+
const installRecordDir = path.join(options.dest, ".flow-agents");
|
|
382
|
+
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
383
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as Record<string, string>;
|
|
384
|
+
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "claude-code", global: true };
|
|
385
|
+
const recordPath = path.join(installRecordDir, "install.json");
|
|
386
|
+
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
387
|
+
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
388
|
+
fs.renameSync(recordTmp, recordPath);
|
|
389
|
+
console.log(`Flow Agents global hooks merged for claude-code in ${options.dest}`);
|
|
390
|
+
return 0;
|
|
391
|
+
}
|
|
392
|
+
// --global for opencode: merge FA opencode.json into the global opencode config dir.
|
|
393
|
+
// Global path: ~/.config/opencode/opencode.json (honor XDG_CONFIG_HOME).
|
|
394
|
+
// Test isolation: FLOW_AGENTS_USER_OPENCODE_CONFIG points to the opencode.json FILE.
|
|
395
|
+
if (options.global && options.runtime === "opencode") {
|
|
396
|
+
const bundle = ensureBundle(options.runtime);
|
|
397
|
+
const sourcePath = path.join(bundle, "opencode.json");
|
|
398
|
+
if (!fs.existsSync(sourcePath)) {
|
|
399
|
+
console.error(`flow-agents init: bundle opencode.json missing: ${sourcePath}`);
|
|
400
|
+
return 1;
|
|
401
|
+
}
|
|
402
|
+
const managed = JSON.parse(fs.readFileSync(sourcePath, "utf8")) as Record<string, unknown>;
|
|
403
|
+
fs.mkdirSync(options.dest, { recursive: true });
|
|
404
|
+
// The global opencode.json lives directly at dest/opencode.json.
|
|
405
|
+
const destConfigPath = path.join(options.dest, "opencode.json");
|
|
406
|
+
const installMergePath = path.join(root, "scripts", "install-merge.js");
|
|
407
|
+
const _require = createRequire(import.meta.url);
|
|
408
|
+
const { mergeSettings } = _require(installMergePath) as { mergeSettings: (a: Record<string, unknown>, b: Record<string, unknown>) => Record<string, unknown> };
|
|
409
|
+
let existing: Record<string, unknown> = {};
|
|
410
|
+
if (fs.existsSync(destConfigPath)) {
|
|
411
|
+
try { existing = JSON.parse(fs.readFileSync(destConfigPath, "utf8")) as Record<string, unknown>; } catch { existing = {}; }
|
|
412
|
+
}
|
|
413
|
+
const merged = mergeSettings(existing, managed);
|
|
414
|
+
const tmp = `${destConfigPath}.tmp.${process.pid}`;
|
|
415
|
+
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}
|
|
416
|
+
`, "utf8");
|
|
417
|
+
fs.renameSync(tmp, destConfigPath);
|
|
418
|
+
// Write version stamp.
|
|
419
|
+
const installRecordDir = path.join(options.dest, ".flow-agents");
|
|
420
|
+
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
421
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as Record<string, string>;
|
|
422
|
+
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "opencode", global: true };
|
|
423
|
+
const recordPath = path.join(installRecordDir, "install.json");
|
|
424
|
+
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
425
|
+
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}
|
|
426
|
+
`, "utf8");
|
|
427
|
+
fs.renameSync(recordTmp, recordPath);
|
|
428
|
+
console.log(`Flow Agents global config merged for opencode in ${options.dest}`);
|
|
429
|
+
return 0;
|
|
430
|
+
}
|
|
431
|
+
// --global for codex: run install-codex-home.sh to install into the isolated Codex HOME.
|
|
432
|
+
// The codex --global path is ~/.flow-agents/codex (the isolated home IS codex's global).
|
|
433
|
+
// Pass through telemetry/console args and honor --dest override for sandbox testing.
|
|
434
|
+
if (options.global && options.runtime === "codex") {
|
|
435
|
+
const codexHomeScript = path.join(root, "scripts", "install-codex-home.sh");
|
|
436
|
+
if (!fs.existsSync(codexHomeScript)) {
|
|
437
|
+
console.error(`flow-agents init: install-codex-home.sh missing: ${codexHomeScript}`);
|
|
438
|
+
return 1;
|
|
439
|
+
}
|
|
440
|
+
const scriptArgs: string[] = [options.dest];
|
|
441
|
+
for (const sink of options.telemetrySinks) scriptArgs.push("--telemetry-sink", sink);
|
|
442
|
+
if (options.consoleUrl) scriptArgs.push("--console-url", options.consoleUrl);
|
|
443
|
+
if (options.consoleEndpoint) scriptArgs.push("--console-endpoint", options.consoleEndpoint);
|
|
444
|
+
if (options.consoleTokenFile) scriptArgs.push("--console-token-file", options.consoleTokenFile);
|
|
445
|
+
if (options.consoleTenant) scriptArgs.push("--console-tenant", options.consoleTenant);
|
|
446
|
+
const result = spawnSync("bash", [codexHomeScript, ...scriptArgs], { env: { ...process.env }, encoding: "utf8", stdio: "inherit" });
|
|
447
|
+
if (result.error) {
|
|
448
|
+
console.error(`flow-agents init: unable to run install-codex-home.sh: ${result.error.message}`);
|
|
449
|
+
return 1;
|
|
450
|
+
}
|
|
451
|
+
return result.status ?? 1;
|
|
452
|
+
}
|
|
453
|
+
// --global for pi: NOT_VERIFIED (no documented global dir). Warn and fall through to workspace install.
|
|
454
|
+
if (options.global && options.runtime === "pi") {
|
|
455
|
+
console.warn(
|
|
456
|
+
`flow-agents init: NOT_VERIFIED: pi has no documented global config directory. ` +
|
|
457
|
+
`The --global flag for pi is not verified against pi documentation. ` +
|
|
458
|
+
`Falling back to workspace default destination: ${options.dest}`
|
|
459
|
+
);
|
|
306
460
|
}
|
|
307
461
|
const bundle = ensureBundle(options.runtime);
|
|
308
462
|
const installed = installBundle(bundle, options);
|
|
@@ -350,38 +504,82 @@ function normalizeDogfoodRuntime(value: string | undefined): DogfoodRuntime | un
|
|
|
350
504
|
}
|
|
351
505
|
|
|
352
506
|
/**
|
|
353
|
-
* Write the claude-code hook-wiring artifacts into dest.
|
|
507
|
+
* Write the claude-code hook-wiring artifacts into dest using merge semantics.
|
|
354
508
|
* Reads dist/claude-code/.claude/settings.json (generated by build-bundles),
|
|
355
|
-
* strips the permissive-mode permission keys (defaultMode, skipDangerousModePermissionPrompt)
|
|
356
|
-
*
|
|
509
|
+
* strips the permissive-mode permission keys (defaultMode, skipDangerousModePermissionPrompt)
|
|
510
|
+
* from the managed bundle settings, then MERGES into any existing dest settings.json —
|
|
511
|
+
* preserving user keys, auth, and non-flow-agents hooks. Writes version stamp.
|
|
357
512
|
*/
|
|
358
513
|
function dogfoodClaudeCode(bundleRoot: string, dest: string): void {
|
|
359
514
|
const sourcePath = path.join(bundleRoot, ".claude", "settings.json");
|
|
360
515
|
if (!fs.existsSync(sourcePath)) throw new Error(`dogfood: bundle settings missing: ${sourcePath}`);
|
|
361
|
-
const
|
|
516
|
+
const managed = JSON.parse(fs.readFileSync(sourcePath, "utf8")) as Record<string, unknown>;
|
|
362
517
|
// Remove permissive defaults that are only appropriate for installed workspaces.
|
|
363
518
|
// These keys must not be present in the source repo's .claude/settings.json.
|
|
364
|
-
delete
|
|
365
|
-
delete
|
|
519
|
+
delete managed["permissions"];
|
|
520
|
+
delete managed["skipDangerousModePermissionPrompt"];
|
|
366
521
|
const outDir = path.join(dest, ".claude");
|
|
367
522
|
fs.mkdirSync(outDir, { recursive: true });
|
|
368
|
-
|
|
523
|
+
const destSettingsPath = path.join(outDir, "settings.json");
|
|
524
|
+
// Merge: read existing, strip FA hooks, append new FA hooks, preserve all other keys.
|
|
525
|
+
const installMergePath = path.join(root, "scripts", "install-merge.js");
|
|
526
|
+
const _require = createRequire(import.meta.url);
|
|
527
|
+
const { mergeSettings } = _require(installMergePath) as { mergeSettings: (a: Record<string, unknown>, b: Record<string, unknown>) => Record<string, unknown> };
|
|
528
|
+
let existing: Record<string, unknown> = {};
|
|
529
|
+
if (fs.existsSync(destSettingsPath)) {
|
|
530
|
+
try { existing = JSON.parse(fs.readFileSync(destSettingsPath, "utf8")) as Record<string, unknown>; } catch { existing = {}; }
|
|
531
|
+
}
|
|
532
|
+
const merged = mergeSettings(existing, managed);
|
|
533
|
+
const tmp = `${destSettingsPath}.tmp.${process.pid}`;
|
|
534
|
+
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
535
|
+
fs.renameSync(tmp, destSettingsPath);
|
|
536
|
+
// Write version stamp.
|
|
537
|
+
const installRecordDir = path.join(dest, ".flow-agents");
|
|
538
|
+
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
539
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as Record<string, string>;
|
|
540
|
+
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "claude-code" };
|
|
541
|
+
const recordPath = path.join(installRecordDir, "install.json");
|
|
542
|
+
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
543
|
+
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
544
|
+
fs.renameSync(recordTmp, recordPath);
|
|
369
545
|
}
|
|
370
546
|
|
|
371
547
|
/**
|
|
372
|
-
* Write the codex hook-wiring artifacts into dest.
|
|
373
|
-
* Reads dist/codex/.codex/hooks.json and
|
|
548
|
+
* Write the codex hook-wiring artifacts into dest using merge semantics.
|
|
549
|
+
* Reads dist/codex/.codex/hooks.json and MERGES into any existing dest hooks.json —
|
|
550
|
+
* preserving user non-FA hook groups. Writes version stamp.
|
|
374
551
|
* The monolithic .codex/config.toml is not written here because it contains
|
|
375
552
|
* workspace settings (approvals_reviewer, features) that would override the
|
|
376
|
-
* developer's existing codex configuration. Only the hooks file is
|
|
553
|
+
* developer's existing codex configuration. Only the hooks file is merged.
|
|
377
554
|
*/
|
|
378
555
|
function dogfoodCodex(bundleRoot: string, dest: string): void {
|
|
379
556
|
const sourcePath = path.join(bundleRoot, ".codex", "hooks.json");
|
|
380
557
|
if (!fs.existsSync(sourcePath)) throw new Error(`dogfood: bundle hooks.json missing: ${sourcePath}`);
|
|
381
|
-
const
|
|
558
|
+
const managed = JSON.parse(fs.readFileSync(sourcePath, "utf8")) as Record<string, unknown>;
|
|
382
559
|
const outDir = path.join(dest, ".codex");
|
|
383
560
|
fs.mkdirSync(outDir, { recursive: true });
|
|
384
|
-
|
|
561
|
+
const destHooksPath = path.join(outDir, "hooks.json");
|
|
562
|
+
// Merge: read existing, strip FA hook-groups, append new FA hook-groups, preserve user groups.
|
|
563
|
+
const installMergePath = path.join(root, "scripts", "install-merge.js");
|
|
564
|
+
const _require = createRequire(import.meta.url);
|
|
565
|
+
const { mergeSettings } = _require(installMergePath) as { mergeSettings: (a: Record<string, unknown>, b: Record<string, unknown>) => Record<string, unknown> };
|
|
566
|
+
let existing: Record<string, unknown> = {};
|
|
567
|
+
if (fs.existsSync(destHooksPath)) {
|
|
568
|
+
try { existing = JSON.parse(fs.readFileSync(destHooksPath, "utf8")) as Record<string, unknown>; } catch { existing = {}; }
|
|
569
|
+
}
|
|
570
|
+
const merged = mergeSettings(existing, managed);
|
|
571
|
+
const tmp = `${destHooksPath}.tmp.${process.pid}`;
|
|
572
|
+
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
573
|
+
fs.renameSync(tmp, destHooksPath);
|
|
574
|
+
// Write version stamp.
|
|
575
|
+
const installRecordDir = path.join(dest, ".flow-agents");
|
|
576
|
+
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
577
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as Record<string, string>;
|
|
578
|
+
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "codex" };
|
|
579
|
+
const recordPath = path.join(installRecordDir, "install.json");
|
|
580
|
+
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
581
|
+
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
582
|
+
fs.renameSync(recordTmp, recordPath);
|
|
385
583
|
}
|
|
386
584
|
|
|
387
585
|
/**
|
|
@@ -368,10 +368,26 @@ function validateSidecarGroup(inputs: string[], markdown: string[], requireSidec
|
|
|
368
368
|
for (const dir of dirs) {
|
|
369
369
|
const deliver = markdown.find((p) => path.dirname(p) === dir && p.includes("deliver") && !p.includes("plan") && !p.includes("review"));
|
|
370
370
|
const delivered = deliver ? /status:\s*(delivered|accepted|archived)/i.test(readText(deliver)) : true;
|
|
371
|
-
|
|
371
|
+
// ADR 0010 Phase 4b: trust.bundle is the primary artifact at the delivered phase.
|
|
372
|
+
// evidence.json is OPTIONAL when trust.bundle is present (still schema-validated when present).
|
|
373
|
+
// Hard-fail on evidence.json absence only when no trust.bundle exists for a delivered session.
|
|
374
|
+
const hasTrustBundle = fs.existsSync(path.join(dir, "trust.bundle"));
|
|
375
|
+
const evidenceRequired = delivered && !hasTrustBundle;
|
|
376
|
+
for (const name of ["state.json", "acceptance.json", ...(evidenceRequired ? ["evidence.json"] : []), "handoff.json"]) {
|
|
372
377
|
if (!fs.existsSync(path.join(dir, name))) issues.push({ path: path.join(dir, name), message: "required sidecar is missing" });
|
|
373
378
|
}
|
|
374
|
-
|
|
379
|
+
// ADR 0010 Phase 4c: critique.json no longer written; trust.bundle carries critique claims. Accept either.
|
|
380
|
+
if (requireCritique && !fs.existsSync(path.join(dir, "critique.json")) && !fs.existsSync(path.join(dir, "trust.bundle"))) issues.push({ path: path.join(dir, "critique.json"), message: "required sidecar is missing" });
|
|
381
|
+
// ADR 0010 Phase 4c: validate critique claims in trust.bundle (sole verification artifact).
|
|
382
|
+
const trustBundlePath = path.join(dir, "trust.bundle");
|
|
383
|
+
if (requireCritique && fs.existsSync(trustBundlePath)) {
|
|
384
|
+
const { value: bundleValue } = readJson(trustBundlePath);
|
|
385
|
+
if (bundleValue) {
|
|
386
|
+
const claims = Array.isArray(bundleValue.claims) ? bundleValue.claims : [];
|
|
387
|
+
const critiqueClaims = claims.filter((c: any) => c && c.claimType === "workflow.critique.review");
|
|
388
|
+
if (critiqueClaims.some((c: any) => c.value === "fail" || c.status === "disputed")) issues.push({ path: trustBundlePath, message: "required critique must pass" });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
375
391
|
const acceptance = path.join(dir, "acceptance.json");
|
|
376
392
|
if (deliver && fs.existsSync(acceptance)) {
|
|
377
393
|
const expected = definitionAcceptanceCriteria(readText(deliver)).length;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { parseArgs, flagString, flagList } from "../lib/args.js";
|
|
6
|
+
import { root } from "../tools/common.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* flow-agents verify — CI trust anchor for downstream repos.
|
|
10
|
+
*
|
|
11
|
+
* Re-runs canonical verification FRESH in the current environment, then reconciles
|
|
12
|
+
* a delivered trust.bundle's claimed passes against those fresh results. Exits 1
|
|
13
|
+
* on divergence, fresh-verify failure, laundered commands, or no verify configured.
|
|
14
|
+
* Exits 0 on clean pass.
|
|
15
|
+
*
|
|
16
|
+
* This is a thin wrapper around scripts/ci/trust-reconcile.js: it resolves the
|
|
17
|
+
* script from the installed package root (via createRequire) and calls the exported
|
|
18
|
+
* runTrustReconcile() function directly so all output goes to the same process
|
|
19
|
+
* stdout/stderr without an extra subprocess layer.
|
|
20
|
+
*
|
|
21
|
+
* The script ships in the npm package `files` list, so downstream repos always have
|
|
22
|
+
* access to it at the same relative path from the package root.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const _require = createRequire(import.meta.url);
|
|
26
|
+
|
|
27
|
+
type RunTrustReconcileFn = (opts: {
|
|
28
|
+
bundle?: string | null;
|
|
29
|
+
commands?: string[];
|
|
30
|
+
repoRoot?: string | null;
|
|
31
|
+
}) => number;
|
|
32
|
+
|
|
33
|
+
function usage(): void {
|
|
34
|
+
process.stderr.write(
|
|
35
|
+
"usage: flow-agents verify [--commands <cmd[,cmd...]>] [--bundle <path>] [--repo-root <path>]\n" +
|
|
36
|
+
"\n" +
|
|
37
|
+
"Re-runs canonical verification fresh and reconciles a delivered trust.bundle's\n" +
|
|
38
|
+
"claimed passes against CI results. Exits 1 on divergence, fresh-verify failure,\n" +
|
|
39
|
+
"compile-only/no-verify, or laundered commands. Exits 0 on clean pass.\n" +
|
|
40
|
+
"\n" +
|
|
41
|
+
"Options:\n" +
|
|
42
|
+
" --commands <cmd,...> Canonical verify command(s). Comma-separated or repeated.\n" +
|
|
43
|
+
" Falls back to TRUST_RECONCILE_COMMANDS env or\n" +
|
|
44
|
+
" package.json scripts['trust-reconcile-verify'].\n" +
|
|
45
|
+
" No-commands → fail-closed (compile-only refused).\n" +
|
|
46
|
+
" --bundle <path> Delivered trust.bundle or trust.checkpoint.json path.\n" +
|
|
47
|
+
" Falls back to TRUST_RECONCILE_BUNDLE env, then auto-\n" +
|
|
48
|
+
" discovers delivery/trust.bundle or delivery/trust.checkpoint.json.\n" +
|
|
49
|
+
" Absent bundle: only fresh verify is enforced (fail-open).\n" +
|
|
50
|
+
" --repo-root <path> Repository root. Default: TRUST_RECONCILE_REPO_ROOT env or cwd.\n" +
|
|
51
|
+
"\n" +
|
|
52
|
+
"Examples:\n" +
|
|
53
|
+
" # Re-run build+test fresh; reconcile against a delivered bundle:\n" +
|
|
54
|
+
" flow-agents verify --commands 'npm run build,npm test' --bundle delivery/trust.bundle\n" +
|
|
55
|
+
"\n" +
|
|
56
|
+
" # Fresh-verify only (no bundle), using package.json trust-reconcile-verify script:\n" +
|
|
57
|
+
" flow-agents verify\n"
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
62
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
63
|
+
usage();
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { flags } = parseArgs(argv);
|
|
68
|
+
|
|
69
|
+
// --commands may be specified multiple times or comma-separated within a single value.
|
|
70
|
+
const commandsRaw = flagList(flags, "commands");
|
|
71
|
+
const commands = commandsRaw.flatMap((c) => c.split(",").map((s) => s.trim()).filter(Boolean));
|
|
72
|
+
|
|
73
|
+
const bundle = flagString(flags, "bundle") ?? null;
|
|
74
|
+
const repoRoot = flagString(flags, "repo-root") ?? null;
|
|
75
|
+
|
|
76
|
+
// Resolve the trust-reconcile.js script from the installed package root.
|
|
77
|
+
// `root` (from tools/common.ts) walks up from this compiled file's directory until
|
|
78
|
+
// it finds a directory with both package.json and packaging/ — the package root.
|
|
79
|
+
// In a downstream installed package: node_modules/@kontourai/flow-agents/
|
|
80
|
+
// In the dev repo: the repo root itself.
|
|
81
|
+
// scripts/ci/trust-reconcile.js ships in the npm package `files` list.
|
|
82
|
+
const reconcilePath = path.join(root, "scripts", "ci", "trust-reconcile.js");
|
|
83
|
+
|
|
84
|
+
if (!fs.existsSync(reconcilePath)) {
|
|
85
|
+
process.stderr.write(
|
|
86
|
+
`[flow-agents verify] error: trust-reconcile.js not found at ${reconcilePath}\n` +
|
|
87
|
+
"[flow-agents verify] Is the package correctly installed? Expected scripts/ci/trust-reconcile.js.\n"
|
|
88
|
+
);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const { runTrustReconcile } = _require(reconcilePath) as { runTrustReconcile: RunTrustReconcileFn };
|
|
93
|
+
|
|
94
|
+
return runTrustReconcile({ bundle, commands, repoRoot });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Direct-script guard (mirrors pattern from other CLI subcommands).
|
|
98
|
+
const _selfReal = (() => { try { return fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return fileURLToPath(import.meta.url); } })();
|
|
99
|
+
const _argv1Real = (() => { try { return fs.realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
100
|
+
if (_selfReal === _argv1Real) { process.exitCode = await main(); }
|