@ijfw/memory-server 1.4.4 → 1.5.0
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/fixtures/truncation-corpus/_generate-corpus.js +367 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/snapshots/v-midO-1-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/snapshots/v-midO-2-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/snapshots/v-midO-3-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/snapshots/v-midO-4-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/snapshots/v-midO-5-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/snapshots/v-noEv-1-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/snapshots/v-noEv-2-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/snapshots/v-noEv-3-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/snapshots/v-noEv-4-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/snapshots/v-noEv-5-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/snapshots/v-errT-1-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/snapshots/v-errT-3-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/snapshots/v-errT-5-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/target/.ijfw/state/workflow.json +1 -0
- package/package.json +1 -1
- package/src/active-extension-writer.js +144 -64
- package/src/api-client.js +43 -5
- package/src/audit-roster.js +80 -5
- package/src/blackboard.js +298 -6
- package/src/cli-run.js +33 -5
- package/src/codex-agents.js +96 -5
- package/src/cost/aggregator.js +39 -9
- package/src/cost/pricing.js +57 -0
- package/src/cost/readers/gemini.js +1 -1
- package/src/cross-audit-chunker.js +189 -0
- package/src/cross-dispatcher.js +124 -21
- package/src/cross-orchestrator-cli.js +550 -14
- package/src/cross-orchestrator.js +1016 -17
- package/src/cross-project-search.js +195 -9
- package/src/dashboard-client-waves.html +304 -0
- package/src/dashboard-client.html +5 -1
- package/src/dashboard-server.js +73 -0
- package/src/deploy-alerts.js +150 -0
- package/src/design/iframe-bridge.js +242 -0
- package/src/design-companion.js +144 -0
- package/src/dispatch/checkpoint-cli.js +97 -0
- package/src/dispatch/colon-syntax.js +81 -1
- package/src/dispatch/extension.js +26 -2
- package/src/dispatch/registry-cli.js +4 -1
- package/src/dispatch/wave-cli.js +201 -6
- package/src/dispatch/worktree-cli.js +40 -0
- package/src/dispatch-planner.js +97 -2
- package/src/dream/runner.mjs +47 -11
- package/src/dream/stage-runner.js +40 -0
- package/src/dream/state-file.js +102 -0
- package/src/extension-installer.js +70 -24
- package/src/extension-quota-tracker.js +4 -2
- package/src/extension-registry.js +289 -35
- package/src/feedback-detector.js +26 -0
- package/src/fs-lock.js +259 -7
- package/src/gate-result.js +95 -1
- package/src/hero-line.js +86 -5
- package/src/intent-router.js +35 -0
- package/src/lib/a11y-contract.js +117 -0
- package/src/lib/atomic-io.js +29 -8
- package/src/lib/cache-keepalive.js +150 -0
- package/src/lib/jsonl-rotation.js +104 -0
- package/src/lib/lighthouse-pillar.js +121 -0
- package/src/lib/llm-call.js +121 -0
- package/src/lib/playwright-baseline.js +205 -0
- package/src/lib/rekor-bridge.js +221 -0
- package/src/lib/repo-map.js +392 -0
- package/src/lib/shasum-verify.js +164 -0
- package/src/lib/sketches-gc.js +132 -0
- package/src/lib/tmp-suffix.js +62 -0
- package/src/lib/ui-review-runner.js +554 -0
- package/src/lib/uispec-drift.js +301 -0
- package/src/lib/uispec-intake.js +381 -0
- package/src/lib/worktree-guards.js +118 -0
- package/src/lib/worktree-recovery.js +100 -0
- package/src/memory/auto-linker.js +152 -0
- package/src/memory/benchmark.js +498 -0
- package/src/memory/dedup.js +126 -0
- package/src/memory/embedding-cache.js +136 -0
- package/src/memory/fact-extractor.js +168 -0
- package/src/memory/fts5.js +65 -1
- package/src/memory/migrations/004-bitemporal.js +91 -0
- package/src/memory/migrations/005-vector-cache.js +61 -0
- package/src/memory/migrations/006-obsidian-graph.js +46 -0
- package/src/memory/migrations/007-skill-telemetry.js +24 -0
- package/src/memory/migrations/008-write-provenance.js +41 -0
- package/src/memory/obsidian-parser.js +91 -0
- package/src/memory/query-dataview.js +86 -0
- package/src/memory/search.js +10 -0
- package/src/memory/temporal.js +529 -0
- package/src/memory/tokenize.js +10 -0
- package/src/memory-facts-handler.js +37 -0
- package/src/memory-feedback.js +260 -2
- package/src/model-refresh.js +292 -0
- package/src/observability/cost-anomaly.js +166 -0
- package/src/observability/evaluator-checkpoint-contract.js +117 -0
- package/src/observability/trace-id.js +163 -0
- package/src/orchestrator/agents-md-blackboard.js +152 -0
- package/src/orchestrator/checkpoint-contract.md +140 -0
- package/src/orchestrator/debug-trident.js +570 -0
- package/src/orchestrator/merge-block-aware.js +350 -0
- package/src/orchestrator/plan-checker.js +475 -0
- package/src/orchestrator/post-done-runner.js +249 -0
- package/src/orchestrator/review.js +38 -3
- package/src/orchestrator/runtime-loop.js +430 -0
- package/src/orchestrator/skill-telemetry-sink.js +29 -0
- package/src/orchestrator/skill-telemetry.js +37 -0
- package/src/orchestrator/state-events.js +459 -0
- package/src/orchestrator/state-sdk.js +1764 -0
- package/src/orchestrator/status-protocol.js +84 -17
- package/src/orchestrator/subagent-telemetry.js +452 -0
- package/src/orchestrator/termination.js +160 -0
- package/src/orchestrator/verification-gate.js +200 -16
- package/src/orchestrator/wave-state.js +332 -23
- package/src/orchestrator/worktree-provision.js +77 -0
- package/src/override-use-registry.js +111 -5
- package/src/receipts.js +36 -4
- package/src/recovery/checkpoint.js +56 -3
- package/src/recovery/code-fixer.js +656 -0
- package/src/recovery/truncation.js +317 -0
- package/src/redactor.js +75 -6
- package/src/runtime-mediator.js +15 -0
- package/src/sanitizer.js +10 -0
- package/src/search-hybrid.js +139 -0
- package/src/server.js +603 -59
- package/src/swarm/worktree.js +27 -4
- package/src/swarm-config.js +94 -17
- package/src/team/domain-templates/book.json +51 -0
- package/src/team/domain-templates/business.json +41 -0
- package/src/team/domain-templates/content.json +50 -0
- package/src/team/domain-templates/design.json +44 -0
- package/src/team/domain-templates/research.json +41 -0
- package/src/team/domain-templates/software.json +40 -0
- package/src/team/generator.js +278 -3
- package/src/team/modify.js +203 -0
- package/src/team/schemas.js +48 -0
- package/src/update-apply.js +19 -3
|
@@ -14,6 +14,7 @@ import { homedir } from 'node:os';
|
|
|
14
14
|
import { spawnSync } from 'node:child_process';
|
|
15
15
|
import { writeAtomic } from './lib/atomic-io.js';
|
|
16
16
|
import { runCrossOp } from './cross-orchestrator.js';
|
|
17
|
+
import { chunkText, mergeFindings, CHUNKER_DEFAULTS } from './cross-audit-chunker.js';
|
|
17
18
|
import { readReceipts, purgeReceipts } from './receipts.js';
|
|
18
19
|
import { renderHeroLine } from './hero-line.js';
|
|
19
20
|
import { ROSTER, isInstalled, isReachable } from './audit-roster.js';
|
|
@@ -21,16 +22,27 @@ import { aggregatePortfolioFindings } from './cross-project-search.js';
|
|
|
21
22
|
import { runImport, runImportAll, listImporters } from './importers/cli.js';
|
|
22
23
|
import { validateToken } from './lib/token.js';
|
|
23
24
|
import { isVersionStringValid } from './lib/npm-view.js';
|
|
25
|
+
import { verifyShasumCrossSource } from './lib/shasum-verify.js';
|
|
24
26
|
import {
|
|
25
27
|
addBlackboardNote,
|
|
26
28
|
blackboardStatus,
|
|
27
29
|
claimArtifact,
|
|
30
|
+
DEFAULT_CLAIM_TTL_MS,
|
|
31
|
+
evictOrphanedClaims,
|
|
28
32
|
initBlackboard,
|
|
29
33
|
readBlackboard,
|
|
30
34
|
releaseClaim,
|
|
35
|
+
updateClaimHeartbeat,
|
|
31
36
|
writeHandoff,
|
|
32
37
|
} from './blackboard.js';
|
|
33
38
|
import { createTeamAssembly, readTeamAssembly } from './team/generator.js';
|
|
39
|
+
import {
|
|
40
|
+
addTeamRole,
|
|
41
|
+
checkTeamAssembly,
|
|
42
|
+
listTeamRoles,
|
|
43
|
+
removeTeamRole,
|
|
44
|
+
swapTeamRole,
|
|
45
|
+
} from './team/modify.js';
|
|
34
46
|
import {
|
|
35
47
|
blockSwarmTask,
|
|
36
48
|
buildSwarmPlan,
|
|
@@ -274,17 +286,19 @@ function parseCrossAlias(mode, args) {
|
|
|
274
286
|
let only = null;
|
|
275
287
|
let confirm = false;
|
|
276
288
|
let expand = false;
|
|
289
|
+
let chunk = false;
|
|
277
290
|
const positional = [];
|
|
278
291
|
for (let i = 1; i < args.length; i++) {
|
|
279
292
|
const arg = args[i];
|
|
280
293
|
if (arg === '--confirm') confirm = true;
|
|
281
294
|
else if (arg === '--expand') expand = true;
|
|
295
|
+
else if (arg === '--chunk') chunk = true; // v1.5.1 H1.6 — wire chunker
|
|
282
296
|
else if (arg === '--with' && args[i + 1]) only = args[++i];
|
|
283
297
|
else if (arg.startsWith('--with=')) only = arg.slice('--with='.length);
|
|
284
298
|
else if (!arg.startsWith('--')) positional.push(arg);
|
|
285
299
|
}
|
|
286
300
|
const target = mode === 'research' ? positional.join(' ').trim() : positional[0];
|
|
287
|
-
return { cmd: 'cross', mode, target: target || undefined, only, confirm, expand };
|
|
301
|
+
return { cmd: 'cross', mode, target: target || undefined, only, confirm, expand, chunk };
|
|
288
302
|
}
|
|
289
303
|
|
|
290
304
|
function parseCommandAlias(args) {
|
|
@@ -396,6 +410,23 @@ function parseArgsInner(args) {
|
|
|
396
410
|
return { cmd: 'design', sub: args[1] || 'status' };
|
|
397
411
|
}
|
|
398
412
|
|
|
413
|
+
// v1.5.0 wire-W1.D — `ijfw ui-review --spec <path> --scope <dirs>`
|
|
414
|
+
if (args[0] === 'ui-review') {
|
|
415
|
+
const opts = { cmd: 'ui-review', spec: null, scope: null, write: true, gcSketches: false, peerInputs: null };
|
|
416
|
+
for (let i = 1; i < args.length; i++) {
|
|
417
|
+
const a = args[i];
|
|
418
|
+
if ((a === '--spec' || a === '-s') && args[i + 1]) { opts.spec = args[++i]; }
|
|
419
|
+
else if (a.startsWith('--spec=')) opts.spec = a.slice('--spec='.length);
|
|
420
|
+
else if ((a === '--scope' || a === '-S') && args[i + 1]) { opts.scope = args[++i]; }
|
|
421
|
+
else if (a.startsWith('--scope=')) opts.scope = a.slice('--scope='.length);
|
|
422
|
+
else if (a === '--no-write') opts.write = false;
|
|
423
|
+
else if (a === '--gc-sketches') opts.gcSketches = true;
|
|
424
|
+
else if ((a === '--peer-inputs') && args[i + 1]) { opts.peerInputs = args[++i]; }
|
|
425
|
+
else if (a.startsWith('--peer-inputs=')) opts.peerInputs = a.slice('--peer-inputs='.length);
|
|
426
|
+
}
|
|
427
|
+
return opts;
|
|
428
|
+
}
|
|
429
|
+
|
|
399
430
|
if (args[0] === 'blackboard') {
|
|
400
431
|
return { cmd: 'blackboard', sub: args[1] || 'status' };
|
|
401
432
|
}
|
|
@@ -464,14 +495,16 @@ function parseArgsInner(args) {
|
|
|
464
495
|
let only = null;
|
|
465
496
|
let confirm = false;
|
|
466
497
|
let expand = false;
|
|
498
|
+
let chunk = false;
|
|
467
499
|
|
|
468
500
|
for (let i = 3; i < args.length; i++) {
|
|
469
501
|
if (args[i] === '--confirm') { confirm = true; }
|
|
470
502
|
else if (args[i] === '--expand') { expand = true; }
|
|
503
|
+
else if (args[i] === '--chunk') { chunk = true; } // v1.5.1 H1.6 — wire chunker
|
|
471
504
|
else if (args[i] === '--with' && args[i + 1]) { only = args[++i]; }
|
|
472
505
|
}
|
|
473
506
|
|
|
474
|
-
return { cmd: 'cross', mode, target, only, confirm, expand };
|
|
507
|
+
return { cmd: 'cross', mode, target, only, confirm, expand, chunk };
|
|
475
508
|
}
|
|
476
509
|
|
|
477
510
|
return { cmd: 'unknown', raw: args[0] };
|
|
@@ -877,6 +910,15 @@ function cmdPurgeReceipts(projectDir) {
|
|
|
877
910
|
// ranges, and non-existent paths pass through unchanged.
|
|
878
911
|
const TARGET_FILE_SIZE_CAP = 64 * 1024; // 64 KB -- leaves prompt headroom
|
|
879
912
|
|
|
913
|
+
// r17.1 — size thresholds for pre-flight advisory. Inputs under WARN are
|
|
914
|
+
// silent; WARN..CAP get a one-line "this might be slow" advisory; CAP..MAX
|
|
915
|
+
// get a "this WILL be truncated, consider chunking" warning before we fire
|
|
916
|
+
// any auditor (so the user can cancel rather than burn wall time). Anything
|
|
917
|
+
// over MAX would be silently truncated by resolveTarget anyway — the
|
|
918
|
+
// pre-flight check makes that loud.
|
|
919
|
+
const TARGET_FILE_SIZE_WARN = 32 * 1024;
|
|
920
|
+
const TARGET_FILE_SIZE_MAX = 256 * 1024; // beyond this, advise chunking explicitly
|
|
921
|
+
|
|
880
922
|
export function resolveTarget(raw, opts = {}) {
|
|
881
923
|
const cap = typeof opts.sizeCap === 'number' ? opts.sizeCap : TARGET_FILE_SIZE_CAP;
|
|
882
924
|
if (typeof raw !== 'string' || !raw) return raw;
|
|
@@ -921,7 +963,44 @@ export function resolveTarget(raw, opts = {}) {
|
|
|
921
963
|
return `File: ${raw}\n\n${contents}`;
|
|
922
964
|
}
|
|
923
965
|
|
|
924
|
-
|
|
966
|
+
// v1.5.1 H1.6 — chunked-dispatch helpers (audit finding token-optimization.md
|
|
967
|
+
// HIGH-H4 + trident.md HIGH-1, 2/2 consensus). The chunker has shipped (with
|
|
968
|
+
// tests) since r17.1 but was never wired into the CLI. `--chunk` now triggers
|
|
969
|
+
// per-chunk dispatch through runCrossOp + a final Jaccard-dedupe merge.
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Decide whether a target absolute path is large enough to benefit from
|
|
973
|
+
* chunking. Exported for unit-tests.
|
|
974
|
+
*/
|
|
975
|
+
export function shouldChunkFile(absPath, opts = {}) {
|
|
976
|
+
const threshold = typeof opts.threshold === 'number' ? opts.threshold : TARGET_FILE_SIZE_CAP;
|
|
977
|
+
try {
|
|
978
|
+
const st = statSync(absPath);
|
|
979
|
+
if (!st.isFile()) return false;
|
|
980
|
+
return st.size > threshold;
|
|
981
|
+
} catch {
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Read a file and produce the per-chunk target strings the auditors will see.
|
|
988
|
+
* Each chunk is annotated with its index so cross-chunk findings can be
|
|
989
|
+
* reconciled. Exported for unit-tests.
|
|
990
|
+
*/
|
|
991
|
+
export function buildChunkedTargets(absPath, rawTarget, opts = {}) {
|
|
992
|
+
const content = readFileSync(absPath, 'utf8');
|
|
993
|
+
const chunks = chunkText(content, opts);
|
|
994
|
+
return chunks.map((c, i) => ({
|
|
995
|
+
chunkIndex: i,
|
|
996
|
+
total: chunks.length,
|
|
997
|
+
bytesStart: c.start,
|
|
998
|
+
bytesEnd: c.end,
|
|
999
|
+
target: `File: ${rawTarget} [chunk ${i + 1}/${chunks.length}, bytes ${c.start}-${c.end}]\n\n${c.text}`,
|
|
1000
|
+
}));
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
async function cmdCross({ mode, target, only, confirm, expand, chunk }) {
|
|
925
1004
|
const VALID_MODES = ['audit', 'research', 'critique'];
|
|
926
1005
|
if (!mode || !VALID_MODES.includes(mode)) {
|
|
927
1006
|
console.error(`ijfw cross requires a mode: ${VALID_MODES.join(', ')}. Example: ijfw cross audit <file>`);
|
|
@@ -935,6 +1014,114 @@ async function cmdCross({ mode, target, only, confirm, expand }) {
|
|
|
935
1014
|
// Issue #6 fix: substitute file contents for path string when target is a
|
|
936
1015
|
// regular file. Keep the raw target for the user-facing echo line.
|
|
937
1016
|
const rawTarget = target;
|
|
1017
|
+
|
|
1018
|
+
// r17.1 — pre-flight size advisory. Run BEFORE resolveTarget truncates,
|
|
1019
|
+
// so the user sees the real number and can decide to abort + chunk the
|
|
1020
|
+
// input themselves rather than getting a silently-truncated audit.
|
|
1021
|
+
// r17-M3: resolve relative paths against cwd FIRST, matching the same
|
|
1022
|
+
// resolution resolveTarget() uses. Without this, `ijfw cross audit foo.md`
|
|
1023
|
+
// (a relative path that exists) would skip the advisory because
|
|
1024
|
+
// existsSync(rawTarget) probes against the wrong cwd-anchor.
|
|
1025
|
+
try {
|
|
1026
|
+
let probePath = null;
|
|
1027
|
+
if (typeof rawTarget === 'string' && rawTarget.length < 4096) {
|
|
1028
|
+
const resolved = isAbsolute(rawTarget) ? rawTarget : resolve(process.cwd(), rawTarget);
|
|
1029
|
+
if (existsSync(resolved)) probePath = resolved;
|
|
1030
|
+
}
|
|
1031
|
+
if (probePath) {
|
|
1032
|
+
const st = statSync(probePath);
|
|
1033
|
+
if (st.isFile()) {
|
|
1034
|
+
if (st.size > TARGET_FILE_SIZE_MAX) {
|
|
1035
|
+
console.log('');
|
|
1036
|
+
console.log(`Heads up -- target is ${(st.size / 1024).toFixed(1)} KB, larger than the ${(TARGET_FILE_SIZE_MAX / 1024).toFixed(0)} KB recommended max.`);
|
|
1037
|
+
console.log(`Auditors will see only the first ${(TARGET_FILE_SIZE_CAP / 1024).toFixed(0)} KB (truncated). For full coverage, chunk the target into smaller files and audit each, OR pass --chunk to auto-split (see \`ijfw cross --help\`).`);
|
|
1038
|
+
} else if (st.size > TARGET_FILE_SIZE_CAP) {
|
|
1039
|
+
console.log('');
|
|
1040
|
+
console.log(`Note: target is ${(st.size / 1024).toFixed(1)} KB; auditors will see the first ${(TARGET_FILE_SIZE_CAP / 1024).toFixed(0)} KB and a truncation marker. Findings beyond that point will be missed.`);
|
|
1041
|
+
} else if (st.size > TARGET_FILE_SIZE_WARN) {
|
|
1042
|
+
console.log('');
|
|
1043
|
+
console.log(`Note: target is ${(st.size / 1024).toFixed(1)} KB; expect a slower wall time (gemini in particular may push the 90s budget).`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
} catch { /* statSync failure is non-fatal; size advisory is best-effort */ }
|
|
1048
|
+
|
|
1049
|
+
// v1.5.1 H1.6 — chunked dispatch path. When --chunk is set AND the target
|
|
1050
|
+
// is a file larger than the size cap, split via the cross-audit-chunker
|
|
1051
|
+
// (boundary-aware, 10% overlap, Jaccard-dedupe merge) and dispatch each
|
|
1052
|
+
// chunk through runCrossOp separately. Merge findings at the end. Opt-in
|
|
1053
|
+
// because cost scales linearly with chunk count.
|
|
1054
|
+
if (chunk) {
|
|
1055
|
+
let absPath = null;
|
|
1056
|
+
try {
|
|
1057
|
+
if (typeof rawTarget === 'string' && rawTarget.length < 4096) {
|
|
1058
|
+
const resolved = isAbsolute(rawTarget) ? rawTarget : resolve(process.cwd(), rawTarget);
|
|
1059
|
+
if (existsSync(resolved) && statSync(resolved).isFile()) absPath = resolved;
|
|
1060
|
+
}
|
|
1061
|
+
} catch { /* */ }
|
|
1062
|
+
|
|
1063
|
+
if (!absPath) {
|
|
1064
|
+
console.log('');
|
|
1065
|
+
console.log('--chunk requires a file target. Topics, git ranges, and missing paths cannot be chunked.');
|
|
1066
|
+
// Fall through to normal path -- --chunk silently no-ops for non-files
|
|
1067
|
+
} else if (!shouldChunkFile(absPath)) {
|
|
1068
|
+
console.log('');
|
|
1069
|
+
console.log(`--chunk: target is ${(statSync(absPath).size / 1024).toFixed(1)} KB, under the ${(TARGET_FILE_SIZE_CAP / 1024).toFixed(0)} KB chunk threshold; running single-pass audit instead.`);
|
|
1070
|
+
// Fall through to normal path
|
|
1071
|
+
} else {
|
|
1072
|
+
const chunks = buildChunkedTargets(absPath, rawTarget);
|
|
1073
|
+
console.log('');
|
|
1074
|
+
console.log(`--chunk: splitting ${rawTarget} into ${chunks.length} chunks (≈${(CHUNKER_DEFAULTS.chunkSize / 1024).toFixed(0)} KB each, ${(CHUNKER_DEFAULTS.overlap / 1024).toFixed(0)} KB overlap).`);
|
|
1075
|
+
console.log(`Trident dispatches: ${chunks.length} × per-chunk audit. Cost scales linearly.`);
|
|
1076
|
+
|
|
1077
|
+
const perChunkResults = [];
|
|
1078
|
+
const auditorIds = new Set();
|
|
1079
|
+
const projectDir = process.cwd();
|
|
1080
|
+
let firedAny = false;
|
|
1081
|
+
for (const { chunkIndex, total, target: chunkTarget } of chunks) {
|
|
1082
|
+
console.log('');
|
|
1083
|
+
console.log(`[chunk ${chunkIndex + 1}/${total}] dispatching...`);
|
|
1084
|
+
try {
|
|
1085
|
+
const r = await runCrossOp({
|
|
1086
|
+
mode, target: chunkTarget, projectDir,
|
|
1087
|
+
runStamp: new Date().toISOString(), only, confirm, expand,
|
|
1088
|
+
});
|
|
1089
|
+
const findings = Array.isArray(r.merged) ? r.merged : [];
|
|
1090
|
+
perChunkResults.push({ chunkIndex, findings });
|
|
1091
|
+
for (const p of (r.picks || [])) auditorIds.add(p.id);
|
|
1092
|
+
if ((r.picks || []).length > 0) firedAny = true;
|
|
1093
|
+
console.log(`[chunk ${chunkIndex + 1}/${total}] ${findings.length} finding(s) from ${(r.picks || []).length} auditor(s).`);
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
console.log(`[chunk ${chunkIndex + 1}/${total}] dispatch error: ${err.message}`);
|
|
1096
|
+
perChunkResults.push({ chunkIndex, findings: [] });
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const merged = mergeFindings(perChunkResults);
|
|
1101
|
+
console.log('');
|
|
1102
|
+
console.log(`=== Chunked audit complete: ${merged.length} unique finding(s) across ${chunks.length} chunks ===`);
|
|
1103
|
+
console.log(`Auditors fired (union): ${[...auditorIds].join(', ') || '(none)'}`);
|
|
1104
|
+
if (!firedAny) {
|
|
1105
|
+
console.log('No auditors fired -- run `ijfw doctor` to see the install hints.');
|
|
1106
|
+
process.exit(2); // r17.1 — degraded exit code
|
|
1107
|
+
}
|
|
1108
|
+
for (const f of merged) {
|
|
1109
|
+
const sev = (f.severity || 'note').toUpperCase();
|
|
1110
|
+
const cluster = f.clusterSize > 1 ? ` [×${f.clusterSize}]` : '';
|
|
1111
|
+
const tgt = f.target ? ` ${f.target} —` : '';
|
|
1112
|
+
// v1.5.0 wire-W4: widen field fallback to cover description/issue/
|
|
1113
|
+
// detail/note/summary keys auditors emit. Closes the r19 "(no detail)"
|
|
1114
|
+
// dropout that made adjudication a guessing game.
|
|
1115
|
+
const text = f.finding || f.text || f.message ||
|
|
1116
|
+
f.description || f.issue ||
|
|
1117
|
+
f.detail || f.details || f.note || f.summary ||
|
|
1118
|
+
'(no detail)';
|
|
1119
|
+
console.log(` ${sev}${cluster}${tgt} ${text}`);
|
|
1120
|
+
}
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
938
1125
|
target = resolveTarget(target);
|
|
939
1126
|
|
|
940
1127
|
// Polish 6: pre-flight reachability check. If no auditor is wired, give a
|
|
@@ -1006,6 +1193,18 @@ async function cmdCross({ mode, target, only, confirm, expand }) {
|
|
|
1006
1193
|
printFindings(mode, merged);
|
|
1007
1194
|
|
|
1008
1195
|
console.log('\nReceipt logged -- run `ijfw status` to see it.');
|
|
1196
|
+
|
|
1197
|
+
// r17.1 — structured exit code so CI scripts + orchestrator-LLM callers
|
|
1198
|
+
// can detect degraded runs without scraping console output.
|
|
1199
|
+
// exit 0 — all picks contributed productively
|
|
1200
|
+
// exit 2 — at least one pick didn't contribute (timeout / failure / aborted)
|
|
1201
|
+
// exit 3 — zero picks contributed productively (INCONCLUSIVE verdict)
|
|
1202
|
+
// exit 1 is reserved for argv / usage errors (already used above).
|
|
1203
|
+
if (auditorResults && Array.isArray(auditorResults)) {
|
|
1204
|
+
const productive = auditorResults.filter(r => r.counted === true || r.status === null || r.status === 'fallback-used');
|
|
1205
|
+
if (productive.length === 0) process.exit(3);
|
|
1206
|
+
if (productive.length < auditorResults.length) process.exit(2);
|
|
1207
|
+
}
|
|
1009
1208
|
}
|
|
1010
1209
|
|
|
1011
1210
|
// ---------------------------------------------------------------------------
|
|
@@ -1314,16 +1513,100 @@ function cmpSemver(a, b) {
|
|
|
1314
1513
|
|
|
1315
1514
|
function readState() { return readJsonSafe(join(ijfwHome(), 'state.json')) || {}; }
|
|
1316
1515
|
function readSettings() { return readJsonSafe(join(ijfwHome(), 'settings.json')) || {}; }
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1516
|
+
|
|
1517
|
+
// v1.5.0 audit M11 (F-REL-1): writeStateFields was best-effort — readState +
|
|
1518
|
+
// merge + writeAtomic was a TOCTOU window where a parallel `ijfw update`
|
|
1519
|
+
// completion could clobber another writer's `last_applied_version`. If the
|
|
1520
|
+
// write failed silently, the re-entrancy guard (last_applied_version >=
|
|
1521
|
+
// last_latest_seen) would never fire and every subsequent session would
|
|
1522
|
+
// nag-loop until manual `ijfw doctor`.
|
|
1523
|
+
//
|
|
1524
|
+
// Fix: serialise read-modify-write under a sync directory lock so the merge
|
|
1525
|
+
// happens against the latest disk state. We use a tiny inlined sync version
|
|
1526
|
+
// of `withFsLock` (mkdir-with-EEXIST is atomic on POSIX + NTFS) rather than
|
|
1527
|
+
// importing the async `fs-lock.js` — this whole CLI is sync top-to-bottom and
|
|
1528
|
+
// converting just-this-callsite to async would force every caller to await.
|
|
1529
|
+
//
|
|
1530
|
+
// On lock-acquire failure we still attempt the write (better than refusing to
|
|
1531
|
+
// persist) and surface the lock failure as a clearer error.
|
|
1532
|
+
const STATE_LOCK_DIR = () => join(ijfwHome(), '.state.lock');
|
|
1533
|
+
const STATE_LOCK_ACQUIRE_TIMEOUT_MS = 5000;
|
|
1534
|
+
const STATE_LOCK_STALE_MS = 30000;
|
|
1535
|
+
const STATE_LOCK_BACKOFF_START_MS = 25;
|
|
1536
|
+
const STATE_LOCK_BACKOFF_MAX_MS = 250;
|
|
1537
|
+
|
|
1538
|
+
function withStateLockSync(fn) {
|
|
1539
|
+
const lockDir = STATE_LOCK_DIR();
|
|
1540
|
+
// Ensure parent exists; tolerate races.
|
|
1541
|
+
try { mkdirSync(dirname(lockDir), { recursive: true, mode: 0o700 }); } catch { /* */ }
|
|
1542
|
+
|
|
1543
|
+
const deadline = Date.now() + STATE_LOCK_ACQUIRE_TIMEOUT_MS;
|
|
1544
|
+
let staleRecoveryUsed = false;
|
|
1545
|
+
let backoff = STATE_LOCK_BACKOFF_START_MS;
|
|
1546
|
+
let acquired = false;
|
|
1547
|
+
|
|
1548
|
+
while (Date.now() < deadline) {
|
|
1549
|
+
try {
|
|
1550
|
+
mkdirSync(lockDir, { recursive: false });
|
|
1551
|
+
acquired = true;
|
|
1552
|
+
break;
|
|
1553
|
+
} catch (err) {
|
|
1554
|
+
if (err && err.code !== 'EEXIST') {
|
|
1555
|
+
// Real FS error (EACCES, ENOENT on parent we couldn't create) —
|
|
1556
|
+
// surface so the caller can fall back to best-effort write.
|
|
1557
|
+
throw err;
|
|
1558
|
+
}
|
|
1559
|
+
// Stale recovery: if the lock dir is older than STATE_LOCK_STALE_MS,
|
|
1560
|
+
// a previous holder crashed mid-write. Remove + retry once.
|
|
1561
|
+
if (!staleRecoveryUsed) {
|
|
1562
|
+
try {
|
|
1563
|
+
const st = statSync(lockDir);
|
|
1564
|
+
if (Date.now() - st.mtimeMs > STATE_LOCK_STALE_MS) {
|
|
1565
|
+
staleRecoveryUsed = true;
|
|
1566
|
+
rmSync(lockDir, { recursive: true, force: true });
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
} catch { /* lock vanished mid-stat; retry */ }
|
|
1570
|
+
}
|
|
1571
|
+
// Bounded busy-wait. Sync sleep via Atomics is heavy; use a tight loop
|
|
1572
|
+
// with deadline check (typical contention is microseconds in practice).
|
|
1573
|
+
const waitUntil = Date.now() + Math.min(backoff, STATE_LOCK_BACKOFF_MAX_MS, deadline - Date.now());
|
|
1574
|
+
// eslint-disable-next-line no-empty
|
|
1575
|
+
while (Date.now() < waitUntil) {}
|
|
1576
|
+
backoff = Math.min(backoff * 2, STATE_LOCK_BACKOFF_MAX_MS);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
if (!acquired) {
|
|
1581
|
+
// Couldn't acquire — fall back to caller's fn anyway. Better to risk a
|
|
1582
|
+
// racy write than to lose the re-entrancy guard entry.
|
|
1583
|
+
try { return fn(); }
|
|
1584
|
+
finally { /* no lock to release */ }
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1320
1587
|
try {
|
|
1321
|
-
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1588
|
+
return fn();
|
|
1589
|
+
} finally {
|
|
1590
|
+
try { rmSync(lockDir, { recursive: true, force: true }); } catch { /* */ }
|
|
1324
1591
|
}
|
|
1325
1592
|
}
|
|
1326
1593
|
|
|
1594
|
+
function writeStateFields(updates) {
|
|
1595
|
+
const path = join(ijfwHome(), 'state.json');
|
|
1596
|
+
withStateLockSync(() => {
|
|
1597
|
+
// Re-read INSIDE the lock so we don't merge against a stale snapshot.
|
|
1598
|
+
const state = Object.assign(readState(), updates);
|
|
1599
|
+
try {
|
|
1600
|
+
writeAtomic(path, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
|
|
1601
|
+
} catch (e) {
|
|
1602
|
+
// M11: persist failure is now visible AND surfaces which field would
|
|
1603
|
+
// not propagate. Re-entrancy guard relies on last_applied_version;
|
|
1604
|
+
// log explicitly so `ijfw doctor` / a user reading logs can spot it.
|
|
1605
|
+
console.error(`could not persist state.json (re-entrancy guard may not fire next session): ${e.message}`);
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1327
1610
|
function cmdUpdateCheck() {
|
|
1328
1611
|
const state = readState();
|
|
1329
1612
|
const current = state.installed_version || '0.0.0';
|
|
@@ -1370,6 +1653,19 @@ function cmdUpdateVerify() {
|
|
|
1370
1653
|
} else {
|
|
1371
1654
|
console.log(` provenance: NOT VERIFIED (audit signatures exited ${sig.status})`);
|
|
1372
1655
|
}
|
|
1656
|
+
// Second factor: shasum cross-verify (F-SEC-7). Dry-run: report but don't
|
|
1657
|
+
// exit non-zero -- caller is asking "what would happen if I updated?", and
|
|
1658
|
+
// the interactive flow already fails closed on mismatch.
|
|
1659
|
+
const shasumDry = verifyShasumCrossSource(r.version);
|
|
1660
|
+
if (shasumDry.mode === 'verified') {
|
|
1661
|
+
console.log(` shasum cross-verify: VERIFIED (${shasumDry.npmShasum.slice(0, 12)}...)`);
|
|
1662
|
+
} else if (shasumDry.mode === 'mismatch') {
|
|
1663
|
+
console.log(` shasum cross-verify: MISMATCH (npm=${shasumDry.npmShasum} release=${shasumDry.releaseShasum}) -- install would be REFUSED`);
|
|
1664
|
+
} else if (shasumDry.mode === 'advisory') {
|
|
1665
|
+
console.log(` shasum cross-verify: ADVISORY (${shasumDry.message})`);
|
|
1666
|
+
} else {
|
|
1667
|
+
console.log(` shasum cross-verify: ERROR (${shasumDry.message})`);
|
|
1668
|
+
}
|
|
1373
1669
|
console.log('Verification complete.');
|
|
1374
1670
|
process.exit(0);
|
|
1375
1671
|
}
|
|
@@ -1545,6 +1841,37 @@ function cmdUpdateInteractive(opts = {}) {
|
|
|
1545
1841
|
return 1;
|
|
1546
1842
|
}
|
|
1547
1843
|
}
|
|
1844
|
+
// Shasum cross-verify (F-SEC-7): independent second factor on top of
|
|
1845
|
+
// npm-side signatures. Fetches the GitLab release asset shasum and
|
|
1846
|
+
// compares it against npm's dist.shasum for the same version. Mismatch
|
|
1847
|
+
// means we refuse to install; advisory (release shasum unavailable)
|
|
1848
|
+
// requires explicit --yes to proceed.
|
|
1849
|
+
const shasum = verifyShasumCrossSource(r.version);
|
|
1850
|
+
if (shasum.mode === 'verified') {
|
|
1851
|
+
console.log(` Shasum: verified (${shasum.npmShasum.slice(0, 12)}...)`);
|
|
1852
|
+
} else if (shasum.mode === 'mismatch') {
|
|
1853
|
+
console.error(' Shasum: MISMATCH -- refusing install.');
|
|
1854
|
+
console.error(` npm : ${shasum.npmShasum}`);
|
|
1855
|
+
console.error(` release : ${shasum.releaseShasum}`);
|
|
1856
|
+
console.error(' The npm tarball does NOT match the GitLab release asset.');
|
|
1857
|
+
console.error(' This could indicate a compromised registry or release. Aborting.');
|
|
1858
|
+
return 1;
|
|
1859
|
+
} else if (shasum.mode === 'error') {
|
|
1860
|
+
console.error(` Shasum: error -- ${shasum.message}`);
|
|
1861
|
+
console.error(' Refusing install: cannot establish second-factor integrity.');
|
|
1862
|
+
return 1;
|
|
1863
|
+
} else if (shasum.mode === 'advisory') {
|
|
1864
|
+
console.error(` Shasum: ADVISORY -- ${shasum.message}`);
|
|
1865
|
+
if (!opts.yes) {
|
|
1866
|
+
console.error(' Continuing requires --yes (acknowledge missing release shasum).');
|
|
1867
|
+
return 1;
|
|
1868
|
+
}
|
|
1869
|
+
console.error(' Proceeding due to --yes; release shasum could not be verified.');
|
|
1870
|
+
} else {
|
|
1871
|
+
// Unknown mode: fail closed.
|
|
1872
|
+
console.error(` Shasum: unknown mode "${shasum.mode}" -- refusing install.`);
|
|
1873
|
+
return 1;
|
|
1874
|
+
}
|
|
1548
1875
|
// Method dispatch
|
|
1549
1876
|
const method = state.install_method || 'manual';
|
|
1550
1877
|
console.log(` install_method: ${method}`);
|
|
@@ -1637,8 +1964,17 @@ function cmdUpdateInteractive(opts = {}) {
|
|
|
1637
1964
|
console.error(`Update did not complete (exit ${installRes.status}). State not written.`);
|
|
1638
1965
|
return 1;
|
|
1639
1966
|
}
|
|
1640
|
-
// Persist both fields atomically -- single write avoids concurrent-reader inconsistency
|
|
1641
|
-
|
|
1967
|
+
// Persist both fields atomically -- single write avoids concurrent-reader inconsistency.
|
|
1968
|
+
// last_good_shasum records the shasum we just successfully cross-verified +
|
|
1969
|
+
// installed (per docs/SECURITY.md "last_good_shasum is a one-way 'what did
|
|
1970
|
+
// we actually install' record"). Only written when shasum was actually
|
|
1971
|
+
// verified (mode === 'verified'); advisory paths leave the previous value
|
|
1972
|
+
// so we don't poison the record with an unverified hash.
|
|
1973
|
+
const stateUpdate = { last_applied_version: r.version, installed_version: r.version };
|
|
1974
|
+
if (shasum && shasum.mode === 'verified' && shasum.npmShasum) {
|
|
1975
|
+
stateUpdate.last_good_shasum = shasum.npmShasum;
|
|
1976
|
+
}
|
|
1977
|
+
writeStateFields(stateUpdate);
|
|
1642
1978
|
console.log('');
|
|
1643
1979
|
console.log(`IJFW updated to v${r.version}. Run \`ijfw status\` to confirm.`);
|
|
1644
1980
|
return 0;
|
|
@@ -2059,6 +2395,8 @@ if (isMainModule) {
|
|
|
2059
2395
|
cmdDashboard(parsed.sub);
|
|
2060
2396
|
} else if (parsed.cmd === 'design') {
|
|
2061
2397
|
cmdDesign(parsed.sub);
|
|
2398
|
+
} else if (parsed.cmd === 'ui-review') {
|
|
2399
|
+
cmdUiReview(parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2062
2400
|
} else if (parsed.cmd === 'blackboard') {
|
|
2063
2401
|
cmdBlackboard(parsed.sub);
|
|
2064
2402
|
} else if (parsed.cmd === 'team') {
|
|
@@ -2158,6 +2496,69 @@ function openDesignUrl(url) {
|
|
|
2158
2496
|
return res.status ?? 0;
|
|
2159
2497
|
}
|
|
2160
2498
|
|
|
2499
|
+
// v1.5.0 wire-W1.D — `ijfw ui-review` production CLI. Wires the 7-pillar
|
|
2500
|
+
// audit runner into a user-facing command. Args:
|
|
2501
|
+
// --spec <UI-SPEC.md path> required
|
|
2502
|
+
// --scope <comma-sep dirs> required (e.g. "src,components")
|
|
2503
|
+
// --no-write skip writing UI-REVIEW.md (preview mode)
|
|
2504
|
+
// --gc-sketches run sketches-gc as the finalizer
|
|
2505
|
+
// --peer-inputs <json-path> optional axe / lighthouse / playwright
|
|
2506
|
+
// pre-computed outputs (JSON file)
|
|
2507
|
+
// --json machine-readable output (skip narrative)
|
|
2508
|
+
async function cmdUiReview(parsed) {
|
|
2509
|
+
if (!parsed.spec) {
|
|
2510
|
+
console.error('Usage: ijfw ui-review --spec <UI-SPEC.md> --scope <dirs> [--no-write] [--gc-sketches] [--peer-inputs <path>]');
|
|
2511
|
+
process.exit(1);
|
|
2512
|
+
}
|
|
2513
|
+
if (!parsed.scope) {
|
|
2514
|
+
console.error('ui-review: --scope is required (comma-separated dirs, e.g. "src,components")');
|
|
2515
|
+
process.exit(1);
|
|
2516
|
+
}
|
|
2517
|
+
const specPath = isAbsolute(parsed.spec) ? parsed.spec : resolve(process.cwd(), parsed.spec);
|
|
2518
|
+
if (!existsSync(specPath)) {
|
|
2519
|
+
console.error(`ui-review: UI-SPEC not found at ${specPath}`);
|
|
2520
|
+
process.exit(1);
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
let peerInputs = {};
|
|
2524
|
+
if (parsed.peerInputs) {
|
|
2525
|
+
const ppath = isAbsolute(parsed.peerInputs) ? parsed.peerInputs : resolve(process.cwd(), parsed.peerInputs);
|
|
2526
|
+
try { peerInputs = JSON.parse(readFileSync(ppath, 'utf8')); }
|
|
2527
|
+
catch (err) {
|
|
2528
|
+
console.error(`ui-review: failed to read --peer-inputs JSON: ${err.message}`);
|
|
2529
|
+
process.exit(1);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
const { runUiReview } = await import('./lib/ui-review-runner.js');
|
|
2534
|
+
const result = await runUiReview({
|
|
2535
|
+
uiSpecPath: specPath,
|
|
2536
|
+
sourceScope: parsed.scope,
|
|
2537
|
+
projectRoot: process.cwd(),
|
|
2538
|
+
peerInputs,
|
|
2539
|
+
write: parsed.write !== false,
|
|
2540
|
+
gcSketches: !!parsed.gcSketches,
|
|
2541
|
+
});
|
|
2542
|
+
|
|
2543
|
+
if (parsed.json) {
|
|
2544
|
+
console.log(JSON.stringify({
|
|
2545
|
+
topVerdict: result.topVerdict,
|
|
2546
|
+
pillarVerdicts: result.pillarVerdicts,
|
|
2547
|
+
reviewPath: result.reviewPath,
|
|
2548
|
+
parallel: result.parallel,
|
|
2549
|
+
}, null, 2));
|
|
2550
|
+
} else {
|
|
2551
|
+
console.log(`UI review: top-level ${result.topVerdict}`);
|
|
2552
|
+
for (const [pillar, verdict] of Object.entries(result.pillarVerdicts)) {
|
|
2553
|
+
console.log(` - ${pillar.padEnd(12)} ${verdict}`);
|
|
2554
|
+
}
|
|
2555
|
+
if (result.reviewPath) console.log(`Review written to: ${result.reviewPath}`);
|
|
2556
|
+
console.log(`Parallel grader run: wall=${result.parallel.wallMs}ms parallelism=${result.parallel.parallelism}`);
|
|
2557
|
+
}
|
|
2558
|
+
// Exit code: PASS=0, FLAG=0 (advisory), BLOCK=2 (ship-blocker)
|
|
2559
|
+
process.exit(result.topVerdict === 'BLOCK' ? 2 : 0);
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2161
2562
|
function cmdDesign(sub) {
|
|
2162
2563
|
const contentDir = join(homedir(), '.ijfw', 'design-companion', 'content');
|
|
2163
2564
|
mkdirSync(contentDir, { recursive: true });
|
|
@@ -2369,8 +2770,11 @@ function cmdTeam(sub) {
|
|
|
2369
2770
|
if (sub === 'init' || sub === 'create') {
|
|
2370
2771
|
const archetype = optionValue(args, ['--archetype', '--type', '-t']);
|
|
2371
2772
|
const teamName = optionValue(args, ['--name']);
|
|
2773
|
+
// F-FUN-1: --brief lets the Discovery hand-off carry domain signal
|
|
2774
|
+
// when filesystem detection would otherwise collapse to mixed/unknown.
|
|
2775
|
+
const brief = optionValue(args, ['--brief', '-b']) || '';
|
|
2372
2776
|
const force = args.includes('--force');
|
|
2373
|
-
const result = createTeamAssembly(process.cwd(), { archetype, teamName, force });
|
|
2777
|
+
const result = createTeamAssembly(process.cwd(), { archetype, teamName, brief, force });
|
|
2374
2778
|
if (!result.ok) {
|
|
2375
2779
|
if (result.error === 'exists') {
|
|
2376
2780
|
console.error('Team assembly already exists. Re-run with --force to replace .ijfw/team/charter.json and workflow.json.');
|
|
@@ -2405,7 +2809,101 @@ function cmdTeam(sub) {
|
|
|
2405
2809
|
process.exit(0);
|
|
2406
2810
|
}
|
|
2407
2811
|
|
|
2408
|
-
|
|
2812
|
+
// F-FUN-4 (audit-MED-teams-#7): ijfw team list -- enumerate roles.
|
|
2813
|
+
if (sub === 'list') {
|
|
2814
|
+
const result = listTeamRoles(process.cwd());
|
|
2815
|
+
if (!result.ok) {
|
|
2816
|
+
console.error(`team list failed: ${result.error}`);
|
|
2817
|
+
process.exit(1);
|
|
2818
|
+
}
|
|
2819
|
+
console.log(`Team: ${result.team_name || '(unnamed)'}`);
|
|
2820
|
+
if (result.project_archetypes.length) console.log(`Archetypes: ${result.project_archetypes.join(', ')}`);
|
|
2821
|
+
console.log(`Roles (${result.roles.length}):`);
|
|
2822
|
+
for (const role of result.roles) {
|
|
2823
|
+
console.log(` - ${role.name} [${role.role_type}] model=${role.model} effort=${role.effort}`);
|
|
2824
|
+
}
|
|
2825
|
+
process.exit(0);
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
// F-FUN-4: ijfw team add <role-name> --charter <path>
|
|
2829
|
+
if (sub === 'add') {
|
|
2830
|
+
const name = args[0] && !args[0].startsWith('--') ? args[0] : null;
|
|
2831
|
+
const charterPath = optionValue(args, ['--charter', '-c']);
|
|
2832
|
+
if (!charterPath) {
|
|
2833
|
+
console.error('Usage: ijfw team add <role-name> --charter <path-to-role.json>');
|
|
2834
|
+
process.exit(1);
|
|
2835
|
+
}
|
|
2836
|
+
const result = addTeamRole(process.cwd(), { charterPath });
|
|
2837
|
+
if (!result.ok) {
|
|
2838
|
+
console.error(`team add failed: ${result.error}`);
|
|
2839
|
+
if (result.errors) result.errors.forEach((e) => console.error(` - ${e}`));
|
|
2840
|
+
process.exit(1);
|
|
2841
|
+
}
|
|
2842
|
+
console.log(`Added role: ${result.role.name}`);
|
|
2843
|
+
if (name && name !== result.role.name) {
|
|
2844
|
+
console.log(` note: charter declared name "${result.role.name}", ignoring CLI argument "${name}"`);
|
|
2845
|
+
}
|
|
2846
|
+
if (result.codex?.ok) console.log(`Codex agents resynced: ${result.codex.count} (${result.codex.skipped ?? 0} unchanged)`);
|
|
2847
|
+
process.exit(0);
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// F-FUN-4: ijfw team remove <role-name>
|
|
2851
|
+
if (sub === 'remove' || sub === 'rm') {
|
|
2852
|
+
const name = args[0] && !args[0].startsWith('--') ? args[0] : null;
|
|
2853
|
+
if (!name) {
|
|
2854
|
+
console.error('Usage: ijfw team remove <role-name>');
|
|
2855
|
+
process.exit(1);
|
|
2856
|
+
}
|
|
2857
|
+
const result = removeTeamRole(process.cwd(), { name });
|
|
2858
|
+
if (!result.ok) {
|
|
2859
|
+
console.error(`team remove failed: ${result.error}`);
|
|
2860
|
+
if (result.errors) result.errors.forEach((e) => console.error(` - ${e}`));
|
|
2861
|
+
process.exit(1);
|
|
2862
|
+
}
|
|
2863
|
+
console.log(`Removed role: ${result.removed}`);
|
|
2864
|
+
if (result.codex?.ok) console.log(`Codex agents resynced: ${result.codex.count} (${result.codex.skipped ?? 0} unchanged)`);
|
|
2865
|
+
process.exit(0);
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
// F-FUN-4: ijfw team swap <old-role-name> --charter <path>
|
|
2869
|
+
if (sub === 'swap') {
|
|
2870
|
+
const oldName = args[0] && !args[0].startsWith('--') ? args[0] : null;
|
|
2871
|
+
const charterPath = optionValue(args, ['--charter', '-c']);
|
|
2872
|
+
if (!oldName || !charterPath) {
|
|
2873
|
+
console.error('Usage: ijfw team swap <old-role-name> --charter <path-to-role.json>');
|
|
2874
|
+
process.exit(1);
|
|
2875
|
+
}
|
|
2876
|
+
const result = swapTeamRole(process.cwd(), { oldName, charterPath });
|
|
2877
|
+
if (!result.ok) {
|
|
2878
|
+
console.error(`team swap failed: ${result.error}`);
|
|
2879
|
+
if (result.errors) result.errors.forEach((e) => console.error(` - ${e}`));
|
|
2880
|
+
process.exit(1);
|
|
2881
|
+
}
|
|
2882
|
+
console.log(`Swapped ${result.swapped.old} -> ${result.swapped.new}`);
|
|
2883
|
+
if (result.codex?.ok) console.log(`Codex agents resynced: ${result.codex.count} (${result.codex.skipped ?? 0} unchanged)`);
|
|
2884
|
+
process.exit(0);
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
// F-FUN-5 (audit-MED-teams-#13): ijfw team check -- standalone validator.
|
|
2888
|
+
if (sub === 'check' || sub === 'validate') {
|
|
2889
|
+
const report = checkTeamAssembly(process.cwd());
|
|
2890
|
+
if (report.ok) {
|
|
2891
|
+
console.log(`Team assembly OK: ${report.role_count} role(s), ${report.artifact_count} artifact(s)`);
|
|
2892
|
+
process.exit(0);
|
|
2893
|
+
}
|
|
2894
|
+
console.error('Team assembly has issues:');
|
|
2895
|
+
if (!report.has_charter || !report.charter.ok) {
|
|
2896
|
+
console.error(' charter.json:');
|
|
2897
|
+
for (const err of report.charter.errors) console.error(` - ${err}`);
|
|
2898
|
+
}
|
|
2899
|
+
if (!report.has_workflow || !report.workflow.ok) {
|
|
2900
|
+
console.error(' workflow.json:');
|
|
2901
|
+
for (const err of report.workflow.errors) console.error(` - ${err}`);
|
|
2902
|
+
}
|
|
2903
|
+
process.exit(1);
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
console.log('Usage: ijfw team init [--archetype <type>] [--name <team-name>] [--force] | status | list | add <role> --charter <path> | remove <role> | swap <old> --charter <path> | check');
|
|
2409
2907
|
process.exit(1);
|
|
2410
2908
|
}
|
|
2411
2909
|
|
|
@@ -2730,7 +3228,45 @@ function cmdSwarm(sub) {
|
|
|
2730
3228
|
process.exit(0);
|
|
2731
3229
|
}
|
|
2732
3230
|
|
|
2733
|
-
|
|
3231
|
+
// F-REL-1 (H5.3): orphan eviction. Walks active claims and releases any
|
|
3232
|
+
// whose freshness anchor (claimed_at or heartbeat_at) is older than the
|
|
3233
|
+
// TTL. Default TTL is 30 minutes; override with --ttl-min N.
|
|
3234
|
+
if (sub === 'evict-orphans' || sub === 'evict') {
|
|
3235
|
+
const minRaw = optionValue(args, ['--ttl-min', '--ttl', '-t']);
|
|
3236
|
+
const min = Number.parseFloat(minRaw);
|
|
3237
|
+
const ttlMs = Number.isFinite(min) && min > 0 ? Math.floor(min * 60 * 1000) : DEFAULT_CLAIM_TTL_MS;
|
|
3238
|
+
const result = evictOrphanedClaims(process.cwd(), { ttlMs });
|
|
3239
|
+
if (!result.ok) {
|
|
3240
|
+
console.log(`Swarm evict halted: ${result.error}`);
|
|
3241
|
+
process.exit(1);
|
|
3242
|
+
}
|
|
3243
|
+
console.log(`Evicted ${result.count} orphan claim(s) (TTL ${Math.round(ttlMs / 60000)}min)`);
|
|
3244
|
+
for (const item of result.evicted) {
|
|
3245
|
+
console.log(` ${item.id} (${item.agent} -> ${item.artifact_id}, age ${Math.round(item.age_ms / 1000)}s)`);
|
|
3246
|
+
}
|
|
3247
|
+
process.exit(0);
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
// F-REL-1 (H5.3): manual heartbeat ping. Subagents normally invoke this
|
|
3251
|
+
// via the programmatic surface (updateClaimHeartbeat); the CLI form is
|
|
3252
|
+
// for forensic dogfooding from tests + the dashboard preview.
|
|
3253
|
+
if (sub === 'heartbeat') {
|
|
3254
|
+
const taskOrClaim = args[0];
|
|
3255
|
+
const owner = optionValue(args.slice(1), ['--owner', '-o']);
|
|
3256
|
+
const claimId = optionValue(args.slice(1), ['--claim', '--id']);
|
|
3257
|
+
const input = claimId
|
|
3258
|
+
? { claim_id: claimId }
|
|
3259
|
+
: { artifact_id: taskOrClaim, agent: owner };
|
|
3260
|
+
const result = updateClaimHeartbeat(process.cwd(), input);
|
|
3261
|
+
if (!result.ok) {
|
|
3262
|
+
console.log(`Heartbeat halted: ${result.error}`);
|
|
3263
|
+
process.exit(1);
|
|
3264
|
+
}
|
|
3265
|
+
console.log(`Heartbeat ${result.claim.id} at ${result.claim.heartbeat_at}`);
|
|
3266
|
+
process.exit(0);
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
console.log('Usage: ijfw swarm plan | prepare [--append] [--reviews] | tasks | prompt <task-id> [--codex] | start <task-id> [--owner <agent>] | complete <task-id> [--message <text>] | block <task-id> --message <why> | ready <task-id> | status | evict-orphans [--ttl-min N] | heartbeat <artifact-id> --owner <agent>');
|
|
2734
3270
|
process.exit(1);
|
|
2735
3271
|
}
|
|
2736
3272
|
|