@dzhechkov/harness-core 0.3.150 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dz-manifest.json +410 -62
- package/README.md +81 -3
- package/dist/agentdb-index.d.ts.map +1 -1
- package/dist/agentdb-index.js +10 -2
- package/dist/agentdb-index.js.map +1 -1
- package/dist/backlog-embed.d.ts +94 -0
- package/dist/backlog-embed.d.ts.map +1 -0
- package/dist/backlog-embed.js +138 -0
- package/dist/backlog-embed.js.map +1 -0
- package/dist/backlog.d.ts +180 -7
- package/dist/backlog.d.ts.map +1 -1
- package/dist/backlog.js +429 -26
- package/dist/backlog.js.map +1 -1
- package/dist/challenge-panel.d.ts +3 -0
- package/dist/challenge-panel.d.ts.map +1 -1
- package/dist/challenge-panel.js +3 -0
- package/dist/challenge-panel.js.map +1 -1
- package/dist/export-holdout.d.ts +149 -0
- package/dist/export-holdout.d.ts.map +1 -0
- package/dist/export-holdout.js +198 -0
- package/dist/export-holdout.js.map +1 -0
- package/dist/feature-adr-checkpoints.d.ts +127 -0
- package/dist/feature-adr-checkpoints.d.ts.map +1 -1
- package/dist/feature-adr-checkpoints.js +199 -1
- package/dist/feature-adr-checkpoints.js.map +1 -1
- package/dist/feature-adr-routing.d.ts +3 -0
- package/dist/feature-adr-routing.d.ts.map +1 -1
- package/dist/feature-adr-routing.js +3 -0
- package/dist/feature-adr-routing.js.map +1 -1
- package/dist/guard.d.ts +42 -0
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +73 -1
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +16 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -2
- package/dist/index.js.map +1 -1
- package/dist/loop-blobs.generated.d.ts +33 -0
- package/dist/loop-blobs.generated.d.ts.map +1 -0
- package/dist/loop-blobs.generated.js +101 -0
- package/dist/loop-blobs.generated.js.map +1 -0
- package/dist/loop-lint.d.ts +63 -0
- package/dist/loop-lint.d.ts.map +1 -0
- package/dist/loop-lint.js +606 -0
- package/dist/loop-lint.js.map +1 -0
- package/dist/loop-plan.d.ts +416 -0
- package/dist/loop-plan.d.ts.map +1 -0
- package/dist/loop-plan.js +1151 -0
- package/dist/loop-plan.js.map +1 -0
- package/dist/loop-render.d.ts +104 -0
- package/dist/loop-render.d.ts.map +1 -0
- package/dist/loop-render.js +1068 -0
- package/dist/loop-render.js.map +1 -0
- package/dist/loop-trace.d.ts +229 -0
- package/dist/loop-trace.d.ts.map +1 -0
- package/dist/loop-trace.js +614 -0
- package/dist/loop-trace.js.map +1 -0
- package/dist/mutation-gate.d.ts +247 -0
- package/dist/mutation-gate.d.ts.map +1 -0
- package/dist/mutation-gate.js +535 -0
- package/dist/mutation-gate.js.map +1 -0
- package/dist/no-stubs.d.ts +53 -0
- package/dist/no-stubs.d.ts.map +1 -0
- package/dist/no-stubs.js +190 -0
- package/dist/no-stubs.js.map +1 -0
- package/dist/package-skill-layouts.d.ts +67 -0
- package/dist/package-skill-layouts.d.ts.map +1 -0
- package/dist/package-skill-layouts.js +81 -0
- package/dist/package-skill-layouts.js.map +1 -0
- package/dist/patterns.d.ts.map +1 -1
- package/dist/patterns.js +156 -75
- package/dist/patterns.js.map +1 -1
- package/dist/recall-domain-boost.d.ts.map +1 -1
- package/dist/recall-domain-boost.js +6 -0
- package/dist/recall-domain-boost.js.map +1 -1
- package/dist/statusline.d.ts +10 -2
- package/dist/statusline.d.ts.map +1 -1
- package/dist/statusline.js +122 -36
- package/dist/statusline.js.map +1 -1
- package/dist/store-lock.d.ts +108 -0
- package/dist/store-lock.d.ts.map +1 -0
- package/dist/store-lock.js +231 -0
- package/dist/store-lock.js.map +1 -0
- package/dist/workflows.d.ts +16 -22
- package/dist/workflows.d.ts.map +1 -1
- package/dist/workflows.js +17 -98
- package/dist/workflows.js.map +1 -1
- package/package.json +6 -4
- package/sbom.json +1073 -203
- package/src/agentdb-index.ts +10 -1
- package/src/backlog-embed.ts +156 -0
- package/src/backlog.ts +536 -28
- package/src/challenge-panel.ts +4 -0
- package/src/export-holdout.ts +235 -0
- package/src/feature-adr-checkpoints.ts +291 -1
- package/src/feature-adr-routing.ts +4 -0
- package/src/guard.ts +106 -1
- package/src/index.ts +62 -2
- package/src/loop-blobs.generated.ts +114 -0
- package/src/loop-lint.ts +643 -0
- package/src/loop-plan.ts +1419 -0
- package/src/loop-render.ts +1126 -0
- package/src/loop-trace.ts +727 -0
- package/src/mutation-gate.ts +701 -0
- package/src/no-stubs.ts +204 -0
- package/src/package-skill-layouts.ts +107 -0
- package/src/patterns.ts +135 -60
- package/src/recall-domain-boost.ts +6 -0
- package/src/statusline.ts +117 -30
- package/src/store-lock.ts +258 -0
- package/src/workflows.ts +18 -117
package/src/guard.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// counts, store size) that the CLI gathers. No filesystem here → deterministic + unit-testable without a repo.
|
|
13
13
|
|
|
14
14
|
import { type RuleTemplate, type TemplateParams, type ChangeSet, templateFires, validTemplateParams } from './guard-promotion.js';
|
|
15
|
+
import { STUB_MARKERS, STUB_PHRASES, checkNoStubs, type StubWaiver } from './no-stubs.js';
|
|
15
16
|
|
|
16
17
|
export type GuardSeverity = 'hard' | 'soft';
|
|
17
18
|
export type GuardOp = 'publish' | 'teach' | 'consolidate' | 'reindex';
|
|
@@ -46,6 +47,13 @@ export interface GuardResult {
|
|
|
46
47
|
readonly violations: readonly Violation[];
|
|
47
48
|
/** ids of the rules that ran for this op (so a report can show what was checked, not just what failed). */
|
|
48
49
|
readonly checked: readonly string[];
|
|
50
|
+
/**
|
|
51
|
+
* Informational notes (FN-7): things a rule wants ON THE RECORD that are NOT violations and never
|
|
52
|
+
* touch the verdict — e.g. "no-stubs: N changed scannable file(s) not scanned". A fail-open skip
|
|
53
|
+
* that leaves no trace is fail-SILENT, the worst kind by the gate's own cost-of-detection
|
|
54
|
+
* argument; a note is the cheap fix. Present only when non-empty.
|
|
55
|
+
*/
|
|
56
|
+
readonly notes?: readonly string[];
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
/** Facts the CLI injects; each rule reads only the fields it needs. Missing evidence ⇒ that rule is skipped. */
|
|
@@ -79,7 +87,37 @@ export interface GuardFacts {
|
|
|
79
87
|
readonly change?: {
|
|
80
88
|
readonly files: readonly string[];
|
|
81
89
|
readonly contents?: Readonly<Record<string, string>>;
|
|
90
|
+
/**
|
|
91
|
+
* FN-7: how many changed STUB-SCANNABLE files the gatherer did NOT read (deleted, non-regular,
|
|
92
|
+
* oversize, read error, or beyond the file cap). The no-stubs scan stays fail-open on each of
|
|
93
|
+
* them — but the skip must be ON THE RECORD (a GuardResult note), never silent.
|
|
94
|
+
*/
|
|
95
|
+
readonly stubSkipped?: number;
|
|
82
96
|
};
|
|
97
|
+
/**
|
|
98
|
+
* for no-stubs: config waivers from `.dz/guard.json` `stubWaivers: [{path, reason}]` — path-keyed,
|
|
99
|
+
* reason MANDATORY (the feature-adr-setup --guards shape). The scan itself reads `change.files` +
|
|
100
|
+
* `change.contents`, the SAME working-tree diff every other diff-aware rule uses.
|
|
101
|
+
*/
|
|
102
|
+
readonly stubWaivers?: readonly StubWaiver[];
|
|
103
|
+
/**
|
|
104
|
+
* for licence-hold: per package that DECLARES a licence hold (package.json `licenseHold` field —
|
|
105
|
+
* ADR-001 of feature hermes-claude-adaptation), the raw evidence the checker needs. The checker
|
|
106
|
+
* fires only for packs that are actually publishable (`privateFlag !== true`): while `private:true`
|
|
107
|
+
* the npm layer itself refuses, and blocking every unrelated publish for a parked pack would train
|
|
108
|
+
* the --no-guard habit. The moment `private` is dropped without the hold being satisfied, this rule
|
|
109
|
+
* HARD-blocks publish.
|
|
110
|
+
*/
|
|
111
|
+
readonly licenceHold?: readonly {
|
|
112
|
+
readonly name: string;
|
|
113
|
+
readonly privateFlag: boolean;
|
|
114
|
+
/** LICENSE file text; null ⇒ absent. */
|
|
115
|
+
readonly licenseText: string | null;
|
|
116
|
+
/** THIRD_PARTY_NOTICES(.md) text; null ⇒ absent. */
|
|
117
|
+
readonly noticesText: string | null;
|
|
118
|
+
/** package.json `license` field. */
|
|
119
|
+
readonly licenseField: string | null;
|
|
120
|
+
}[];
|
|
83
121
|
readonly lockfile?: {
|
|
84
122
|
readonly parsed: boolean;
|
|
85
123
|
readonly importers?: readonly {
|
|
@@ -191,8 +229,15 @@ export const DEFAULT_RULES: readonly GuardRule[] = [
|
|
|
191
229
|
{ id: 'readme-first', severity: 'soft', ops: ['publish'], description: 'a package with a staged version bump must update its own README.md in the same change (README-first)' },
|
|
192
230
|
{ id: 'lockfile-in-sync', severity: 'soft', ops: ['publish'], description: 'every workspace @dzhechkov/* dependency spec matches the specifier pnpm-lock.yaml records for that importer (a dep bump without a lockfile refresh breaks CI with ERR_PNPM_OUTDATED_LOCKFILE). SOFT-ONLY — a config cannot promote it to HARD' },
|
|
193
231
|
{ id: 'store-bloat-cap', severity: 'soft', ops: ['teach', 'consolidate'], description: 'the learned store is within its size cap' },
|
|
232
|
+
// Description ASSEMBLED from STUB_MARKERS so guard.ts itself stays clean under the scan it defines
|
|
233
|
+
// (structural self-exemption — tested in no-stubs.test.ts).
|
|
234
|
+
{ id: 'no-stubs', severity: 'soft', ops: ['publish'], description: `an unfinished-stub marker (${STUB_MARKERS.join('/')} / "${STUB_PHRASES.join('", "')}") left in a CHANGED file — any unwaived match means the change ships incomplete; waive per line with "no-stubs: <reason>" or per path in .dz/guard.json stubWaivers (reason MANDATORY)` },
|
|
235
|
+
{ id: 'licence-hold', severity: 'hard', ops: ['publish'], description: 'a pack that declares a licence hold (package.json.licenseHold — ADR-001 hermes-claude-adaptation) must not become publishable until the hold is satisfied: LICENSE present without the PENDING grant placeholder, a Grant-Confirmation URL, non-empty THIRD_PARTY_NOTICES, and a clean SPDX license field' },
|
|
194
236
|
];
|
|
195
237
|
|
|
238
|
+
/** The exact placeholder LICENSE marker the licence-hold rule looks for (shared with pack tests). */
|
|
239
|
+
export const LICENCE_HOLD_PENDING_MARKER = '<!-- PENDING:';
|
|
240
|
+
|
|
196
241
|
/**
|
|
197
242
|
* Secret patterns — high-signal, low-false-positive. Each is anchored to a real credential shape, so ordinary
|
|
198
243
|
* prose does not trip it. Extend deliberately (a broad `[A-Za-z0-9]{32}` would flag every hash).
|
|
@@ -317,6 +362,53 @@ const CHECKERS: Record<string, (f: GuardFacts, sev: GuardSeverity) => Violation[
|
|
|
317
362
|
if (!s || !Number.isFinite(s.count) || !Number.isFinite(s.cap) || s.cap <= 0) return [];
|
|
318
363
|
return s.count <= s.cap ? [] : [{ rule: 'store-bloat-cap', severity: sev, detail: `learned store has ${s.count} patterns, over the cap of ${s.cap} — run dz consolidate --prune-noise` }];
|
|
319
364
|
},
|
|
365
|
+
'no-stubs': (f, sev) => {
|
|
366
|
+
// Backlog 0b403a0106103901 (Karpathy-Michaels rule XI), layer-1 on the cost-of-detection ladder:
|
|
367
|
+
// a stub marker left in a file of THIS change-set means the task shipped incomplete. SCOPE is the
|
|
368
|
+
// change-set on purpose (MEASURED: a whole-tree scan is ~78% ancient-marker noise and would be
|
|
369
|
+
// switched off within a day). SOFT: it warns — an unfinished stub is a fact worth a record, but a
|
|
370
|
+
// publish of an unrelated package must not be blocked by it. Fail-open on missing evidence: no
|
|
371
|
+
// `change` fact, or a changed file whose contents were not gathered, reports nothing.
|
|
372
|
+
const ch = f.change;
|
|
373
|
+
if (!ch || typeof ch !== 'object' || !Array.isArray(ch.files)) return [];
|
|
374
|
+
return checkNoStubs(ch.files, ch.contents, f.stubWaivers).map((s) => ({
|
|
375
|
+
rule: 'no-stubs',
|
|
376
|
+
severity: sev,
|
|
377
|
+
detail: s.kind === 'marker'
|
|
378
|
+
? `${s.path}:${s.line}: stub marker "${s.detail}" in a changed file — finish it, or waive the line with "no-stubs: <reason>" (or .dz/guard.json stubWaivers with a reason)`
|
|
379
|
+
: `${s.path}${s.line > 0 ? ':' + s.line : ''}: ${s.detail}`,
|
|
380
|
+
}));
|
|
381
|
+
},
|
|
382
|
+
'licence-hold': (f, sev) => {
|
|
383
|
+
// ADR-001 (hermes-claude-adaptation), machine-armed: a pack parked behind a licence hold carries
|
|
384
|
+
// `private:true` (the npm layer refuses it) + a `licenseHold` trigger field. THIS rule is the second
|
|
385
|
+
// belt — it fires the moment the pack becomes PUBLISHABLE (private dropped) while the hold is still
|
|
386
|
+
// unsatisfied, and refuses `dz publish` HARD. Clearing the hold means: real LICENSE (no PENDING
|
|
387
|
+
// placeholder, a Grant-Confirmation URL), non-empty THIRD_PARTY_NOTICES, and a clean SPDX id in
|
|
388
|
+
// package.json.license. The trigger field itself stays — a satisfied hold passes with it in place.
|
|
389
|
+
const out: Violation[] = [];
|
|
390
|
+
for (const p of f.licenceHold ?? []) {
|
|
391
|
+
if (!p || typeof p.name !== 'string') continue;
|
|
392
|
+
if (p.privateFlag === true) continue; // npm refuses it; other packs' publishes stay unblocked
|
|
393
|
+
const problems: string[] = [];
|
|
394
|
+
const lic = (p.licenseText ?? '').trim();
|
|
395
|
+
const notices = (p.noticesText ?? '').trim();
|
|
396
|
+
if (lic.length < 40) problems.push('LICENSE missing or empty');
|
|
397
|
+
else {
|
|
398
|
+
if (lic.includes(LICENCE_HOLD_PENDING_MARKER)) problems.push('LICENSE still carries the PENDING grant placeholder');
|
|
399
|
+
if (!/Grant-Confirmation:\s*https?:\/\/\S+/.test(lic)) problems.push('LICENSE has no "Grant-Confirmation: <url>" line');
|
|
400
|
+
}
|
|
401
|
+
if (notices.length < 40) problems.push('THIRD_PARTY_NOTICES missing or empty');
|
|
402
|
+
const field = (p.licenseField ?? '').trim();
|
|
403
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9.+-]*(\s+(AND|OR|WITH)\s+[A-Za-z0-9][A-Za-z0-9.+-]*)*$/.test(field) || /^UNLICENSED$/i.test(field)) {
|
|
404
|
+
problems.push(`package.json license "${field}" is not a clean SPDX id`);
|
|
405
|
+
}
|
|
406
|
+
if (problems.length > 0) {
|
|
407
|
+
out.push({ rule: 'licence-hold', severity: sev, detail: `${p.name}: publishable (private flag removed) but the ADR-001 licence hold is UNSATISFIED — ${problems.join('; ')}` });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return out;
|
|
411
|
+
},
|
|
320
412
|
};
|
|
321
413
|
|
|
322
414
|
/**
|
|
@@ -431,7 +523,17 @@ export function evaluateGuard(facts: GuardFacts, rules: readonly GuardRule[] = D
|
|
|
431
523
|
}
|
|
432
524
|
}
|
|
433
525
|
const verdict: GuardVerdict = violations.some((v) => v.severity === 'hard') ? 'block' : violations.length > 0 ? 'warn' : 'pass';
|
|
434
|
-
|
|
526
|
+
// FN-7 — the fail-open skips go ON THE RECORD. The no-stubs scan is fail-open by contract
|
|
527
|
+
// (missing contents ⇒ nothing reported), but a skip nobody can see is fail-SILENT. One aggregate
|
|
528
|
+
// note, computed AFTER the verdict so it can never block or warn: information, not a violation.
|
|
529
|
+
const notes: string[] = [];
|
|
530
|
+
if (checked.includes('no-stubs')) {
|
|
531
|
+
const skipped = facts.change?.stubSkipped;
|
|
532
|
+
if (typeof skipped === 'number' && Number.isFinite(skipped) && skipped > 0) {
|
|
533
|
+
notes.push(`no-stubs: ${skipped} changed scannable file(s) not scanned (deleted/oversize/unreadable/beyond the file cap) — the stub scan is fail-open, so this is a coverage gap on the record, not a violation`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return { op, verdict, violations, checked, ...(notes.length > 0 ? { notes } : {}) };
|
|
435
537
|
}
|
|
436
538
|
|
|
437
539
|
/** An append-only audit record. `ts` is injected by the caller (no wall-clock here → deterministic tests). */
|
|
@@ -440,6 +542,8 @@ export interface GuardAuditRecord {
|
|
|
440
542
|
readonly op: GuardOp;
|
|
441
543
|
readonly verdict: GuardVerdict;
|
|
442
544
|
readonly violations: readonly Violation[];
|
|
545
|
+
/** informational notes (FN-7 — e.g. the no-stubs skipped-files note); on the record, never a verdict input. */
|
|
546
|
+
readonly notes?: readonly string[];
|
|
443
547
|
/** set when the operator overrode a block with `--force <reason>` — the override is logged, never silent. */
|
|
444
548
|
readonly override?: { readonly forced: true; readonly reason: string };
|
|
445
549
|
}
|
|
@@ -451,6 +555,7 @@ export function auditRecord(result: GuardResult, ts: string, override?: { reason
|
|
|
451
555
|
op: result.op,
|
|
452
556
|
verdict: result.verdict,
|
|
453
557
|
violations: result.violations,
|
|
558
|
+
...(Array.isArray(result.notes) && result.notes.length > 0 ? { notes: result.notes } : {}),
|
|
454
559
|
...(override && typeof override.reason === 'string' ? { override: { forced: true, reason: override.reason } } : {}),
|
|
455
560
|
};
|
|
456
561
|
}
|
package/src/index.ts
CHANGED
|
@@ -27,7 +27,34 @@ export {
|
|
|
27
27
|
} from './parity.js';
|
|
28
28
|
export type { RuntimeCapability, FeatureForm, ParityFeature, ParityCell, ParityMatrixRow } from './parity.js';
|
|
29
29
|
export * from './operations.js';
|
|
30
|
+
// workflows.ts: the ADR-005 templates are RETIRED (feature loop-designer, AM-6) — the module is a
|
|
31
|
+
// deprecation shim (empty WORKFLOW_NAMES). BREAKING for external harness-core consumers of
|
|
32
|
+
// WorkflowTemplate/WORKFLOWS/getWorkflow — deliberately channeled through the 0.x MINOR bump and
|
|
33
|
+
// named in the CHANGELOG; replacement: dz workflow init/validate/render + workflow-lint/-trace.
|
|
30
34
|
export * from './workflows.js';
|
|
35
|
+
// loop-designer (feature loop-designer): loop-plan/1 schema + generator + lint + trace planes.
|
|
36
|
+
export * from './loop-plan.js';
|
|
37
|
+
export * from './loop-render.js';
|
|
38
|
+
// loop-lint: EXPLICIT export list (QE round-2 G14) — `dominators`, the deliberately-WEAKER
|
|
39
|
+
// analysis kept in src/loop-lint.ts solely as AM-1's mutation seam, is NOT part of the published
|
|
40
|
+
// API surface; the in-package tests reach it via the module path directly.
|
|
41
|
+
export {
|
|
42
|
+
lint,
|
|
43
|
+
lintExitCode,
|
|
44
|
+
postDominators,
|
|
45
|
+
LINT_RULES,
|
|
46
|
+
SIZE_BUDGET_WARN_LINES,
|
|
47
|
+
type LintVerdict,
|
|
48
|
+
type LintSeverity,
|
|
49
|
+
type LintMode,
|
|
50
|
+
type LintFinding,
|
|
51
|
+
type LintRun,
|
|
52
|
+
type LintRuleId,
|
|
53
|
+
type LintOptions,
|
|
54
|
+
} from './loop-lint.js';
|
|
55
|
+
export * from './loop-trace.js';
|
|
56
|
+
export { BLOBS as LOOP_BLOBS, LOOP_BLOB_NAMES, BLOB_COVERAGE_MANIFEST } from './loop-blobs.generated.js';
|
|
57
|
+
export type { LoopBlob } from './loop-blobs.generated.js';
|
|
31
58
|
export * from './sign.js';
|
|
32
59
|
export * from './skill-schema.js';
|
|
33
60
|
export { createSkill } from './create-skill.js';
|
|
@@ -38,9 +65,15 @@ export { sweepSkillDrift, syncCanonicalSkill } from './skill-drift.js';
|
|
|
38
65
|
export type { SweepResult, DriftedSkill, SyncResult, SyncCanonicalOptions } from './skill-drift.js';
|
|
39
66
|
export { benchmarkSkill, benchmarkSkills, compareSkills } from './benchmark.js';
|
|
40
67
|
export { buildRegistry, searchRegistry, filterByCategory, skillPackBaseDirs, discoverSkillPackDirs } from './registry.js';
|
|
68
|
+
// Package skill-layout resolution (feature dz-install-npx-init) — the ONE seam that knows where an
|
|
69
|
+
// npm package keeps its skills (flat / templates/.claude/skills / skills). `cmdInstall` calls it;
|
|
70
|
+
// `dz init`/`dz registry` are the filed follow-up consumers.
|
|
71
|
+
export * from './package-skill-layouts.js';
|
|
41
72
|
export { recommend } from './recommend.js';
|
|
42
73
|
export { pretrain } from './pretrain.js';
|
|
43
74
|
export { loadPatterns, loadSessions, computePatternBoost, readLearningConfig, readMemoryLearningConfig, BOOST_CAP, recordPattern, loadStorePatternsSync, loadStoreRecords, patternToRecord, recordToPattern, patternRecordId, patternIdentityOf, dreamRecordId, isMirrorableLearning, consolidateSessions, recallPatterns, pruneNoisePatterns, removePatternsByIds, snapshotStore, readReinforcementState, encodeReinforcementState, reinforcePattern, updateReinforcementState, storeStats, lessonDeltaReport, lessonDeltaMap, readQuarantineState, encodeQuarantineState, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns } from './patterns.js';
|
|
75
|
+
export { withStoreLock, withStoreLockSync, storeLockPath, StoreLockTimeoutError, StoreLockCompromisedError, STALE_LOCK_MS, LOCK_TIMEOUT_MS } from './store-lock.js';
|
|
76
|
+
export type { StoreLockOptions } from './store-lock.js';
|
|
44
77
|
export type { PatternRecord, SessionRecord, LearningConfig, MemoryLearningConfig, LoadOptions, ConsolidateResult, ConsolidateOptions, SqliteBackendMode, RecallHit, SessionsSource, PruneNoiseResult, RemovePatternsResult, SnapshotStoreResult, ReinforcementState, ReinforcePatternResult, StoreStats, LessonDeltaReport, LessonDeltaRow, QuarantineState, PromoteResult, QuarantineExpiryCandidate } from './patterns.js';
|
|
45
78
|
export { DEFAULT_REINFORCE_THRESHOLD, NoopLearningBackend, NativeReinforcementBackend, resolveLearningBackend, isLearningSignalBackend } from './learning-backend.js';
|
|
46
79
|
export type { LearningSignalBackend, LearningSignalStats, LearningSample, SignalCandidate, EnhanceContext, TrainingResult, LearningBackendMode } from './learning-backend.js';
|
|
@@ -94,7 +127,7 @@ export type {
|
|
|
94
127
|
TeachGuardResult,
|
|
95
128
|
} from './vector-tier.js';
|
|
96
129
|
export { runSetup, generateHooksConfig, generateAgentdbWriter, writerVersionOf, AGENTDB_WRITER_VERSION } from './setup.js';
|
|
97
|
-
export { statuslineData, readFeatureAdrState, writeFeatureAdrState, featureAdrStatePath } from './statusline.js';
|
|
130
|
+
export { statuslineData, readFeatureAdrState, writeFeatureAdrState, featureAdrStateDir, featureAdrStatePath } from './statusline.js';
|
|
98
131
|
export type { StatuslineData, FeatureAdrState, WriteFeatureAdrStateInput } from './statusline.js';
|
|
99
132
|
export { indexPatternsToAgentdb, resolveAgentdbPath, searchAgentdbPatterns, listAgentdbDzIds, resolveAgentdbEmbedder, cosineSimilarity, importVectorsToAgentdb, reindexAgentdbRows, bumpAgentdbUses, clearAgentdbQuarantine, deleteAgentdbByDzIds, readAgentdbRowsByTaskType, DZ_OWNED_TASK_TYPES } from './agentdb-index.js';
|
|
100
133
|
export type { AgentdbSearchHit, AgentdbSearchResult, AgentdbImportRow } from './agentdb-index.js';
|
|
@@ -153,8 +186,16 @@ export {
|
|
|
153
186
|
parseCheckpointRead,
|
|
154
187
|
checkpointReadCmd,
|
|
155
188
|
checkpointAppendCmd,
|
|
189
|
+
TRAINPAIR_SCHEMA_VERSION,
|
|
190
|
+
TRAINPAIR_MAX_IO_CHARS,
|
|
191
|
+
TRAINPAIR_PRIVACY_NOTE,
|
|
192
|
+
trainingPairFamily,
|
|
193
|
+
buildTrainingPair,
|
|
194
|
+
serializeTrainingPair,
|
|
195
|
+
trainingPairPath,
|
|
196
|
+
trainingPairAppendCmd,
|
|
156
197
|
} from './feature-adr-checkpoints.js';
|
|
157
|
-
export type { CheckpointStage, CheckpointEntry, ResumeMode, ResumeDecision, ParsedCheckpointRead } from './feature-adr-checkpoints.js';
|
|
198
|
+
export type { CheckpointStage, CheckpointEntry, ResumeMode, ResumeDecision, ParsedCheckpointRead, TrainingPairFamily, TrainingPair, TrainingPairEvaluation, TrainingPairProvenance, TrainingPairTruncation } from './feature-adr-checkpoints.js';
|
|
158
199
|
export {
|
|
159
200
|
DOMAIN_LIFT_EXACT,
|
|
160
201
|
DOMAIN_LIFT_RELATED,
|
|
@@ -166,6 +207,16 @@ export {
|
|
|
166
207
|
renderDomainCutNote,
|
|
167
208
|
} from './recall-domain-boost.js';
|
|
168
209
|
export type { DomainMatch, DomainBoostResult } from './recall-domain-boost.js';
|
|
210
|
+
export {
|
|
211
|
+
DEFAULT_HELD_OUT_DOMAINS,
|
|
212
|
+
applyExportHoldout,
|
|
213
|
+
canonicalDomainKey,
|
|
214
|
+
heldOutAfterOptIn,
|
|
215
|
+
renderHoldoutNote,
|
|
216
|
+
renderSharedStoreAdvice,
|
|
217
|
+
decideVectorExport,
|
|
218
|
+
} from './export-holdout.js';
|
|
219
|
+
export type { HoldoutResult, PatternHoldout, VectorExportDecision } from './export-holdout.js';
|
|
169
220
|
export {
|
|
170
221
|
REQE_SCHEMA,
|
|
171
222
|
REQE_SCOPE,
|
|
@@ -514,7 +565,16 @@ export {
|
|
|
514
565
|
// from its artifacts. Descriptive-only, permanently: it never gates.
|
|
515
566
|
export * from './score.js';
|
|
516
567
|
|
|
568
|
+
// Mutation gate (feature ha-mutation-gate) — deliberately break each NAMED protection in a scratch
|
|
569
|
+
// copy, run the suite, REQUIRE red. Proves a test DISCRIMINATES, not merely that it is green.
|
|
570
|
+
// Pure half of `dz mutation-gate`; the copy/run/restore I/O lives in the CLI executor.
|
|
571
|
+
export * from './mutation-gate.js';
|
|
572
|
+
|
|
517
573
|
// Smart Backlog (feature smart-backlog) — goal-directed idea pipeline: capture → semantic dedup
|
|
518
574
|
// against the EXISTING Brain vector engine (ADR-001, no 2nd store) → GoalMap alignment (ADR-003) →
|
|
519
575
|
// weighted roulette (ADR-004) → idea2prd enrich hand-off → stub-first Jira adapter seam (ADR-006).
|
|
520
576
|
export * from './backlog.js';
|
|
577
|
+
|
|
578
|
+
// no-stubs (backlog 0b403a0106103901) — deterministic unfinished-stub-marker scan over the
|
|
579
|
+
// CHANGE-SET, wired as the `no-stubs` SOFT publish guard rule + the feature-adr Step-8 QE item.
|
|
580
|
+
export * from './no-stubs.js';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* @generated by scripts/gen-loop-blobs.mjs — DO NOT EDIT BY HAND.
|
|
4
|
+
* Canonical sources live in harness-core/src (feature-adr-checkpoints.ts,
|
|
5
|
+
* feature-adr-routing.ts, challenge-panel.ts, loop-trace.ts). Edit those, then run:
|
|
6
|
+
* node scripts/gen-loop-blobs.mjs
|
|
7
|
+
* CI runs `node scripts/gen-loop-blobs.mjs --check` (via loop-blobs-regen.test.ts) and
|
|
8
|
+
* fails on any diff — "test the generator once" (ADR-004).
|
|
9
|
+
*
|
|
10
|
+
* Blob roster: checkpoints, training-pairs (default OFF — the health-advisor PHI
|
|
11
|
+
* lesson, AM-9), model-resolver (auto-included when any step.model is set),
|
|
12
|
+
* usage-probes, codex-dispatch, challenge-panel, trace (the 7th — carries the
|
|
13
|
+
* dispatch/settle seq emitter). All seven names are FIXED; a new subsystem is a new
|
|
14
|
+
* blob name plus an ADR note, never a silent rename.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface LoopBlob {
|
|
18
|
+
name: string;
|
|
19
|
+
version: string;
|
|
20
|
+
contentHash: string;
|
|
21
|
+
sourcePath: string;
|
|
22
|
+
/** Blobs whose declarations this blob depends on when co-injected (shared-helper dedup). */
|
|
23
|
+
requires: string[];
|
|
24
|
+
exports: string[];
|
|
25
|
+
code: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const LOOP_BLOB_NAMES = ["checkpoints","training-pairs","model-resolver","usage-probes","codex-dispatch","challenge-panel","trace","ha-consult-router"] as const;
|
|
29
|
+
|
|
30
|
+
/** Workflow files the regen-diff gate covers TODAY (AM-5 honest scope): exactly the files
|
|
31
|
+
* carrying BEGIN BLOB markers. Stage B (whole-file regeneration of feature-adr.js) is a
|
|
32
|
+
* tracked dz-backlog item, deliberately NOT claimed here. */
|
|
33
|
+
export const BLOB_COVERAGE_MANIFEST: { coveredWorkflows: string[] } = {
|
|
34
|
+
"coveredWorkflows": [
|
|
35
|
+
".claude/workflows/feature-adr.js",
|
|
36
|
+
".claude/workflows/health-advisor.js",
|
|
37
|
+
"packages/@dzhechkov/skills-feature-adr/templates/.claude/workflows/feature-adr.js"
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const BLOBS: Record<string, LoopBlob> = {
|
|
42
|
+
"checkpoints": {
|
|
43
|
+
name: "checkpoints",
|
|
44
|
+
version: "1.1.0",
|
|
45
|
+
contentHash: "aa730483f52a9f6263751138d4514fe9a6a3f4f191897c86d1e035f3da890574",
|
|
46
|
+
sourcePath: "packages/@dzhechkov/harness-core/src/feature-adr-checkpoints.ts",
|
|
47
|
+
requires: [],
|
|
48
|
+
exports: ["CHECKPOINT_STAGES","STAGE_ARTIFACTS","CHECKPOINT_MAX_RESULT_CHARS","CKPT_SCHEMA_VERSION","fnv1a","fnv1a64","checkpointInputHash","resumeMode","decideCheckpointResume","serializeCheckpoint","CHECKPOINT_LS_SENTINEL","parseCheckpointRead","shellQuote","checkpointReadCmd","checkpointAppendCmd"],
|
|
49
|
+
code: "const CHECKPOINT_STAGES = ['router', 'design', 'plan', 'code', 'qe', 'fleet'];\nconst STAGE_ARTIFACTS = {\n router: null,\n design: '01_requirements.md',\n plan: '06_implementation_plan.md',\n code: '07_code_changes/change_manifest.md',\n qe: '08_qe_report.md',\n fleet: '09_fleet_qe_assessment.md',\n};\nconst CHECKPOINT_MAX_RESULT_CHARS = 12000;\nconst CKPT_SCHEMA_VERSION = 'fa-ckpt-2';\nfunction fnv1a(str) {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;\n }\n return h.toString(16).padStart(8, '0');\n}\nfunction fnv1a64(str) {\n return fnv1a(str) + fnv1a('fa-ckpt-salt' + str);\n}\nfunction checkpointInputHash(stage, parts) {\n return fnv1a64(JSON.stringify([CKPT_SCHEMA_VERSION, stage, ...parts.map((p) => (p === undefined ? null : p))]));\n}\nfunction resumeMode(raw) {\n return raw === 'never' ? 'never' : raw === 'force' ? 'force' : 'auto';\n}\nfunction decideCheckpointResume(opts) {\n if (opts.mode === 'never')\n return { resume: false, reason: 'mode-never' };\n if (!opts.entry || opts.entry.result === null || opts.entry.result === undefined) {\n return { resume: false, reason: 'no-checkpoint' };\n }\n if (opts.entry.inputHash !== opts.inputHash)\n return { resume: false, reason: 'stale-input' };\n if (opts.mode === 'force')\n return { resume: true, reason: 'resumed-force' };\n const required = opts.artifactRel === null ? [] : (typeof opts.artifactRel === 'string' ? [opts.artifactRel] : opts.artifactRel);\n for (const rel of required) {\n if (!opts.listing.has(rel))\n return { resume: false, reason: 'artifact-missing' };\n }\n return { resume: true, reason: 'resumed' };\n}\nfunction serializeCheckpoint(stage, inputHash, result) {\n if (result === null || result === undefined)\n return null;\n let line;\n try {\n line = JSON.stringify({ stage, inputHash, result });\n }\n catch {\n return null;\n }\n if (typeof line !== 'string' || line.length > CHECKPOINT_MAX_RESULT_CHARS)\n return null;\n return line;\n}\nconst CHECKPOINT_LS_SENTINEL = '---FA-CKPT-LS---';\nfunction parseCheckpointRead(text) {\n const out = { entries: {}, listing: new Set(), malformedLines: 0 };\n const raw = String(text ?? '');\n const lines = raw.split('\\n');\n const sentinelAt = lines.findIndex((l) => l.trim() === CHECKPOINT_LS_SENTINEL);\n const body = sentinelAt === -1 ? lines : lines.slice(0, sentinelAt);\n const ls = sentinelAt === -1 ? [] : lines.slice(sentinelAt + 1);\n for (const line of body) {\n const t = line.trim();\n if (t === '')\n continue;\n try {\n const e = JSON.parse(t);\n if (e && typeof e === 'object' && typeof e.stage === 'string' && typeof e.inputHash === 'string' && 'result' in e && e.result !== null && e.result !== undefined) {\n out.entries[e.stage] = e;\n }\n else {\n if (e && typeof e === 'object' && typeof e.stage === 'string')\n delete out.entries[e.stage];\n out.malformedLines++;\n }\n }\n catch {\n out.malformedLines++;\n }\n }\n for (const line of ls) {\n const t = line.trim();\n if (t !== '')\n out.listing.add(t);\n }\n return out;\n}\nfunction shellQuote(s) {\n return \"'\" + String(s).replace(/'/g, \"'\\\\''\") + \"'\";\n}\nfunction checkpointReadCmd(fdirAbs) {\n const q = shellQuote(fdirAbs);\n return ('cat ' + q + '/.fa-state/checkpoints.jsonl 2>/dev/null || true; ' +\n \"echo '\" + CHECKPOINT_LS_SENTINEL + \"'; \" +\n 'cd ' + q + ' 2>/dev/null && find . -maxdepth 2 -type f 2>/dev/null | sed \"s|^\\\\./||\" || true');\n}\nfunction checkpointAppendCmd(fdirAbs, line) {\n const dir = shellQuote(fdirAbs + '/.fa-state');\n const file = shellQuote(fdirAbs + '/.fa-state/checkpoints.jsonl');\n return 'mkdir -p ' + dir + \" && printf '%s\\\\n' \" + shellQuote(line) + ' >> ' + file;\n}",
|
|
50
|
+
},
|
|
51
|
+
"training-pairs": {
|
|
52
|
+
name: "training-pairs",
|
|
53
|
+
version: "1.1.0",
|
|
54
|
+
contentHash: "740b733d3d995e7031590f1707b442d0f0f9d7d585ada6f37bbf08c5db3351e9",
|
|
55
|
+
sourcePath: "packages/@dzhechkov/harness-core/src/feature-adr-checkpoints.ts",
|
|
56
|
+
requires: ["checkpoints"],
|
|
57
|
+
exports: ["TRAINPAIR_SCHEMA_VERSION","TRAINPAIR_MAX_IO_CHARS","trainingPairFamily","trainingPairPath","TRAINPAIR_PRIVACY_NOTE","buildTrainingPair","serializeTrainingPair","trainingPairAppendCmd","decideCaptureMode","captureFailureRecord","trainingPairBackfillCmd","TP_BACKFILL_OK","TP_BACKFILL_SKIP"],
|
|
58
|
+
code: "function decideCaptureMode(opts) {\n if (!opts.enabled)\n return 'skip-disabled';\n if (!Number.isInteger(opts.recordCount) || opts.recordCount <= 0)\n return 'skip-empty';\n return opts.resumed ? 'backfill' : 'capture';\n}\nfunction captureFailureRecord(stage, mode, reason, detail) {\n const normalizedStage = typeof stage === 'string' && stage.trim() !== '' ? stage : 'unknown';\n const normalizedMode = mode === 'capture' || mode === 'backfill' || mode === 'skip-disabled' || mode === 'skip-empty'\n ? mode\n : null;\n const normalizedReason = reason === 'threw' || reason === 'unserializable' || reason === 'unverified' || reason === 'backfill-unverified' || reason === 'empty-output'\n ? reason\n : 'threw';\n let normalizedDetail = null;\n if (detail !== null && detail !== undefined) {\n try {\n const text = String(detail);\n if (text !== '')\n normalizedDetail = text.length > 500 ? text.slice(0, 500) + '…' : text;\n }\n catch {\n normalizedDetail = null;\n }\n }\n return { stage: normalizedStage, mode: normalizedMode, reason: normalizedReason, detail: normalizedDetail };\n}\nconst TRAINPAIR_SCHEMA_VERSION = 'fa-trainpair-2';\nconst TRAINPAIR_MAX_IO_CHARS = 48000;\nfunction trainingPairFamily(spec) {\n return /codex|gpt|openai/i.test(String(spec ?? '')) ? 'codex' : 'claude';\n}\nfunction trainingPairPath(slug, stage) {\n return '.dz/fa-training/' + slug + '/' + stage + '.jsonl';\n}\nconst TRAINPAIR_PRIVACY_NOTE = \"feature-adr TRAINING PAIRS (backlog 70e0f083): per-stage SFT records - STAGE INPUT (full prompt/context) -> STAGE OUTPUT (artifact/result) -> EVALUATION (QE grade + injected lessons) with model+family provenance; one JSONL file per stage per slug. PRIVACY: pairs may contain TARGET-REPO CODE and full prompts. This directory is NOT gitignored yet by explicit owner decision - review contents before sharing or publishing anything that embeds it. ts is the CAPTURE time. On a record with captureMode: 'backfill' that is the RECONSTRUCTION time, NOT the stage's observation time — the original stage's timing lives in that run's .fa-state checkpoint.\";\nfunction coerceText(v) {\n if (typeof v === 'string')\n return v;\n if (v === null || v === undefined)\n return '';\n try {\n const s = JSON.stringify(v);\n return typeof s === 'string' ? s : String(v);\n }\n catch {\n return String(v);\n }\n}\nfunction buildTrainingPair(opts) {\n let input = coerceText(opts.input);\n let output = coerceText(opts.output);\n let truncated = null;\n if (input.length + output.length > TRAINPAIR_MAX_IO_CHARS) {\n truncated = { inputChars: input.length, outputChars: output.length, inputHash: fnv1a64(input), outputHash: fnv1a64(output) };\n const half = Math.floor(TRAINPAIR_MAX_IO_CHARS / 2);\n let inKeep = input.length;\n let outKeep = output.length;\n if (outKeep <= half)\n inKeep = TRAINPAIR_MAX_IO_CHARS - outKeep;\n else if (inKeep <= half)\n outKeep = TRAINPAIR_MAX_IO_CHARS - inKeep;\n else {\n inKeep = half;\n outKeep = TRAINPAIR_MAX_IO_CHARS - half;\n }\n if (inKeep < input.length)\n input = input.slice(0, inKeep) + '\\n…[TRUNCATED ' + (truncated.inputChars - inKeep) + ' chars — full-text fnv1a64=' + truncated.inputHash + ']';\n if (outKeep < output.length)\n output = output.slice(0, outKeep) + '\\n…[TRUNCATED ' + (truncated.outputChars - outKeep) + ' chars — full-text fnv1a64=' + truncated.outputHash + ']';\n }\n const ev = opts.evaluation || {};\n const pv = opts.provenance || {};\n return {\n schema: TRAINPAIR_SCHEMA_VERSION,\n slug: opts.slug,\n stage: opts.stage,\n ts: opts.ts === undefined ? null : opts.ts,\n input,\n output,\n evaluation: {\n grade: typeof ev.grade === 'string' && ev.grade.trim() !== '' ? ev.grade : null,\n gradedBy: typeof ev.gradedBy === 'string' && ev.gradedBy !== '' ? ev.gradedBy : null,\n lessonsInjected: Array.isArray(ev.lessonsInjected) ? ev.lessonsInjected.filter((s) => typeof s === 'string' && s !== '') : [],\n },\n provenance: {\n model: typeof pv.model === 'string' && pv.model !== '' ? pv.model : 'unknown',\n family: pv.family === 'claude' || pv.family === 'codex' ? pv.family : trainingPairFamily(pv.model),\n role: typeof pv.role === 'string' && pv.role !== '' ? pv.role : 'unknown',\n tokens: typeof pv.tokens === 'number' && Number.isFinite(pv.tokens) ? pv.tokens : null,\n minutes: typeof pv.minutes === 'number' && Number.isFinite(pv.minutes) ? pv.minutes : null,\n },\n truncated,\n captureMode: opts.captureMode === 'backfill' ? 'backfill' : 'capture',\n resumed: opts.resumed === true,\n };\n}\nfunction serializeTrainingPair(pair) {\n try {\n const line = JSON.stringify(pair);\n return typeof line === 'string' ? line : null;\n }\n catch {\n return null;\n }\n}\nfunction trainingPairAppendCmd(repoAbs, slug, stage, line) {\n const dirAbs = repoAbs + '/.dz/fa-training/' + slug;\n const readmeAbs = repoAbs + '/.dz/fa-training/README.md';\n const fileAbs = dirAbs + '/' + stage + '.jsonl';\n return ('mkdir -p ' + shellQuote(dirAbs) +\n ' && { [ -f ' + shellQuote(readmeAbs) + ' ] || printf \\'%s\\\\n\\' ' + shellQuote(TRAINPAIR_PRIVACY_NOTE) + ' > ' + shellQuote(readmeAbs) + '; }' +\n \" && printf '%s\\\\n' \" + shellQuote(line) + ' >> ' + shellQuote(fileAbs));\n}\nconst TP_BACKFILL_OK = 'TP-BACKFILL-OK';\nconst TP_BACKFILL_SKIP = 'TP-BACKFILL-SKIP';\nconst TP_BACKFILL_DUP = 'TP-BACKFILL-DUP';\nfunction trainingPairBackfillCmd(repoAbs, slug, stage, lines, markKey) {\n if (typeof repoAbs !== 'string' || repoAbs === '')\n return null;\n if (typeof slug !== 'string' || slug === '')\n return null;\n if (typeof stage !== 'string' || stage === '')\n return null;\n if (!Array.isArray(lines) || lines.length === 0 || !lines.every(line => typeof line === 'string' && line !== ''))\n return null;\n const dirAbs = repoAbs + '/.dz/fa-training/' + slug;\n const readmeAbs = repoAbs + '/.dz/fa-training/README.md';\n const fileAbs = dirAbs + '/' + stage + '.jsonl';\n const markDir = repoAbs + '/.dz/fa-training/.backfill-marks';\n const markStage = stage.replace(/\\.\\./g, '_').replace(/\\//g, '_');\n const resolvedMarkKey = markKey === undefined ? fnv1a64(stage + '\\0' + lines.join('\\n')) : markKey;\n const markPath = markDir + '/' + markStage + '-' + resolvedMarkKey;\n const appends = lines\n .map(line => \"printf '%s\\\\n' \" + shellQuote(line) + ' >> ' + shellQuote(fileAbs))\n .join(' && ');\n return ('mkdir -p ' + shellQuote(dirAbs) +\n ' && { [ -f ' + shellQuote(readmeAbs) + ' ] || printf \\'%s\\\\n\\' ' + shellQuote(TRAINPAIR_PRIVACY_NOTE) + ' > ' + shellQuote(readmeAbs) + '; }' +\n ' && mkdir -p ' + shellQuote(markDir) +\n ' && if mkdir ' + shellQuote(markPath) + ' 2>/dev/null; then ' +\n 'if [ -f ' + shellQuote(fileAbs) + ' ]; then echo ' + shellQuote(TP_BACKFILL_SKIP) +\n '; else { ' + appends + ' && echo ' + shellQuote(TP_BACKFILL_OK) + '; } || { rmdir ' + shellQuote(markPath) + ' 2>/dev/null; false; }; fi' +\n '; else echo ' + shellQuote(TP_BACKFILL_DUP) + '; fi');\n}",
|
|
59
|
+
},
|
|
60
|
+
"model-resolver": {
|
|
61
|
+
name: "model-resolver",
|
|
62
|
+
version: "1.0.0",
|
|
63
|
+
contentHash: "dd4020b613dc3814aaaf5c6a07a82bff844680f3be43d2943754f46b0b1bea3c",
|
|
64
|
+
sourcePath: "packages/@dzhechkov/harness-core/src/feature-adr-routing.ts",
|
|
65
|
+
requires: [],
|
|
66
|
+
exports: ["specToOpts","resolveStageModel","KNOWN_CODEX","mergeOpts","stageLabel","modelLabel"],
|
|
67
|
+
code: "const OVERRIDE_REASONING = {\n router: 'high',\n requirements: 'xhigh',\n research: 'xhigh',\n adr: 'xhigh',\n ideation: 'xhigh',\n ddd: 'xhigh',\n architecture: 'xhigh',\n plan: 'xhigh',\n code: 'xhigh',\n qe: 'high',\n fleet: 'high',\n};\nfunction topCodexId(env) {\n let top = env.CODEX_MODEL;\n if (top === 'auto') {\n const ids = Object.keys(KNOWN_CODEX);\n for (let i = 0; i < ids.length; i++) {\n if (ids[i] !== 'auto')\n top = ids[i] || top;\n }\n }\n return top;\n}\nconst KNOWN_CODEX = { auto: 1, 'gpt-5.5': 1, 'gpt-5.6': 1, 'gpt-5.6-sol': 1 };\nconst CLAUDE_NAMES = { fable: 1, opus: 1, sonnet: 1, haiku: 1 };\nconst VALID_REASONING = { none: 1, minimal: 1, low: 1, medium: 1, high: 1, xhigh: 1 };\nconst DEFAULT_MODELS = {\n router: 'fable',\n requirements: 'sonnet',\n research: 'sonnet',\n adr: 'opus',\n ideation: 'sonnet',\n ddd: 'opus',\n architecture: 'opus',\n plan: 'sonnet',\n code: null,\n qe: null,\n fleet: 'sonnet',\n};\nfunction specToOpts(spec, env) {\n const log = env.log || function () { };\n if (!spec)\n return {};\n const parts = String(spec).split(':');\n const head = parts[0] || '';\n if (head === 'codex') {\n let id = parts[1] || env.CODEX_MODEL;\n if (id !== 'auto' && !KNOWN_CODEX[id]) {\n log('models: unknown codex id ' + id + ' — using ' + env.CODEX_MODEL);\n id = env.CODEX_MODEL;\n }\n let reasoning = parts[2] || 'high';\n if (!VALID_REASONING[reasoning]) {\n log('models: unknown reasoning ' + reasoning + ' — using high');\n reasoning = 'high';\n }\n return { agentType: 'codex:codex-rescue', codexModel: id, _reasoning: reasoning };\n }\n if (CLAUDE_NAMES[head])\n return { model: head };\n log('models: unknown spec ' + spec + ' — session-inherited');\n return {};\n}\nfunction resolveCoderSpec(env) {\n if (env.CODER === 'codex' || env.CODER === 'codex-fallback')\n return 'codex:' + env.CODEX_MODEL + ':high';\n return 'opus';\n}\nfunction coderIsCodex(env) {\n if (env.CODER === 'codex' || env.CODER === 'codex-fallback')\n return true;\n const codeSpec = env.MODELS.code;\n if (codeSpec && String(codeSpec).split(':')[0] === 'codex')\n return true;\n return false;\n}\nfunction resolveQeSpec(env) {\n if (coderIsCodex(env))\n return 'opus';\n const CODEX_AVAILABLE = env.codexAvailable !== false;\n if (!CODEX_AVAILABLE)\n return 'opus';\n return 'codex:' + topCodexId(env) + ':high';\n}\nfunction routingRequested(env) {\n return (Object.keys(env.MODELS).length > 0 ||\n env.PLANNER === 'codex' ||\n env.CODER === 'codex' ||\n env.CODER === 'codex-fallback' ||\n env.QE_REVIEWER === 'codex' ||\n env.QE_REVIEWER === 'codex-fallback');\n}\nfunction resolveStageModel(stage, env) {\n if (env.usageOverride) {\n const r = (env.usageReasoning && env.usageReasoning[stage]) || OVERRIDE_REASONING[stage] || 'high';\n const o = specToOpts('codex:' + topCodexId(env) + ':' + r, env);\n o._usageSwitched = true;\n return o;\n }\n let spec = env.MODELS[stage];\n if (spec === undefined) {\n if (!routingRequested(env))\n return {};\n spec = DEFAULT_MODELS[stage];\n }\n if (stage === 'code' && (spec === null || spec === undefined))\n return specToOpts(resolveCoderSpec(env), env);\n if (stage === 'qe' && (spec === null || spec === undefined))\n return specToOpts(resolveQeSpec(env), env);\n return specToOpts(spec, env);\n}\nfunction modelLabel(opts) {\n if (opts && opts.agentType === 'codex:codex-rescue') {\n const base = 'codex:' + opts.codexModel + ':' + opts._reasoning;\n return opts._usageSwitched ? base + ' (usage-switched)' : base;\n }\n if (opts && opts.model)\n return opts.model;\n return 'session';\n}\nfunction stageLabel(base, opts) {\n const m = modelLabel(opts);\n return m === 'session' ? base : base + ' · ' + m;\n}\nfunction mergeOpts(base, extra) {\n const out = {};\n for (const k in base)\n out[k] = base[k];\n for (const k in extra)\n out[k] = extra[k];\n return out;\n}",
|
|
68
|
+
},
|
|
69
|
+
"usage-probes": {
|
|
70
|
+
name: "usage-probes",
|
|
71
|
+
version: "1.0.0",
|
|
72
|
+
contentHash: "4ae2504a50fe05aad4383e223d460b21396805b17dfefab50e60022d58cdd19d",
|
|
73
|
+
sourcePath: "packages/@dzhechkov/harness-core/src/feature-adr-routing.ts",
|
|
74
|
+
requires: ["model-resolver"],
|
|
75
|
+
exports: ["decideUsageAction","OVERRIDE_REASONING","topCodexId"],
|
|
76
|
+
code: "function decideUsageAction(prevOverride, signal, threshold) {\n if (signal === null || signal === undefined) {\n if (prevOverride)\n return { override: true, action: 'keep' };\n return { override: true, action: 'fail-safe-switch' };\n }\n const s = signal.sessionPct;\n const w = signal.weeklyPct;\n const sKnown = typeof s === 'number' && isFinite(s) && s >= 0;\n const wKnown = typeof w === 'number' && isFinite(w) && w >= 0;\n if ((sKnown && s >= threshold) || (wKnown && w >= threshold)) {\n return { override: true, action: prevOverride ? 'keep' : 'switch' };\n }\n if (sKnown && wKnown) {\n return { override: false, action: prevOverride ? 'restore' : 'none' };\n }\n return { override: prevOverride, action: prevOverride ? 'keep' : 'none' };\n}",
|
|
77
|
+
},
|
|
78
|
+
"codex-dispatch": {
|
|
79
|
+
name: "codex-dispatch",
|
|
80
|
+
version: "1.0.0",
|
|
81
|
+
contentHash: "e6a608d2856287aa4db2b2db769d2e7ea3ec76b6c7cf299d2e233b4d9d90d0cf",
|
|
82
|
+
sourcePath: "packages/@dzhechkov/harness-core/src/feature-adr-routing.ts",
|
|
83
|
+
requires: [],
|
|
84
|
+
exports: ["codexDispatchMode","codexExecPlan","needsCodeLandedBarrier","decideCodeLanding"],
|
|
85
|
+
code: "const CODE_LANDING_PIPELINE_PREFIXES = ['features/', '.dz/', '.agentic-qe/', 'roam/'];\nfunction codeLandingEmptySignal(seconds) {\n return 'changed=0 after ' + seconds + 's — genuinely not landed';\n}\nfunction needsCodeLandedBarrier(coderUsed) {\n return coderUsed === 'codex' || coderUsed === 'codex-fallback';\n}\nfunction normalizeCodeLandingPath(path) {\n let p = String(path || '').trim().replace(/\\\\/g, '/');\n while (p.indexOf('./') === 0)\n p = p.slice(2);\n p = p.replace(/\\/+/g, '/');\n if (!p)\n return '';\n if (p[0] === '/')\n return '';\n if (p === '..' || p.indexOf('../') === 0 || p.indexOf('/../') >= 0 || p.endsWith('/..'))\n return '';\n if (/[\\0\\r\\n\\t \"'\\x60$;&|<>*?()[\\]{}!]/.test(p))\n return '';\n if (p.endsWith('/'))\n return '';\n for (const prefix of CODE_LANDING_PIPELINE_PREFIXES) {\n const bare = prefix.slice(0, -1);\n if (p === bare || p.indexOf(prefix) === 0)\n return '';\n }\n return p;\n}\nfunction filterPollableCodePaths(paths) {\n const out = [];\n const seen = new Set();\n for (const path of paths) {\n const normalized = normalizeCodeLandingPath(path);\n if (!normalized || seen.has(normalized))\n continue;\n seen.add(normalized);\n out.push(normalized);\n }\n return out;\n}\nfunction decideCodeLanding(snapshot) {\n const maxWaitMs = Math.max(0, snapshot.maxWaitMs);\n const elapsedMs = Math.max(0, snapshot.elapsedMs);\n const elapsedSeconds = Math.floor(elapsedMs / 1000);\n const expectedPaths = filterPollableCodePaths(snapshot.expectedPaths);\n const changedPaths = filterPollableCodePaths(snapshot.changedEntries.map((entry) => entry.path));\n const changed = new Set(changedPaths);\n const matchedExpectedPaths = expectedPaths.filter((path) => changed.has(path));\n if (expectedPaths.length > 0 && matchedExpectedPaths.length > 0) {\n return {\n status: 'landed',\n changed: matchedExpectedPaths.length,\n elapsedMs,\n elapsedSeconds,\n expectedPaths,\n matchedExpectedPaths,\n changedPaths,\n predicate: 'expected-path',\n qeSignalLine: 'CODEX-LANDING-SIGNAL status=landed changed=' +\n matchedExpectedPaths.length +\n ' after=' +\n elapsedSeconds +\n 's predicate=expected-path matched=' +\n matchedExpectedPaths.join(','),\n };\n }\n if (expectedPaths.length === 0 && changedPaths.length > 0) {\n return {\n status: 'landed',\n changed: changedPaths.length,\n elapsedMs,\n elapsedSeconds,\n expectedPaths,\n matchedExpectedPaths,\n changedPaths,\n predicate: 'any-code-change',\n qeSignalLine: 'CODEX-LANDING-SIGNAL status=landed changed=' +\n changedPaths.length +\n ' after=' +\n elapsedSeconds +\n 's predicate=any-code-change',\n };\n }\n if (elapsedMs < maxWaitMs) {\n return {\n status: 'not-yet-flushed',\n changed: 0,\n elapsedMs,\n elapsedSeconds,\n expectedPaths,\n matchedExpectedPaths: [],\n changedPaths,\n predicate: 'empty-before-timeout',\n qeSignalLine: 'CODEX-LANDING-SIGNAL status=not-yet-flushed changed=0 after ' + elapsedSeconds + 's — not yet flushed',\n };\n }\n const terminalSeconds = Math.ceil(maxWaitMs / 1000);\n return {\n status: 'genuinely-not-landed',\n changed: 0,\n elapsedMs,\n elapsedSeconds: terminalSeconds,\n expectedPaths,\n matchedExpectedPaths: [],\n changedPaths,\n predicate: 'empty-after-timeout',\n qeSignalLine: 'CODEX-LANDING-SIGNAL status=genuinely-not-landed ' + codeLandingEmptySignal(terminalSeconds),\n };\n}\nconst WRAPPER_STAGES = { code: 1, plan: 1 };\nfunction codexDispatchMode(stage) {\n return WRAPPER_STAGES[stage] ? 'wrapper' : 'exec';\n}\nconst CODEX_EXEC_PROMPT_CEILING_CHARS = 24000;\nfunction codexExecPlan(input) {\n if (codexDispatchMode(input.stage) === 'wrapper') {\n return { mode: 'wrapper', reason: 'deliverable is a file written out-of-band' };\n }\n if (!input.probedId) {\n return { mode: 'claude', reason: 'no codex model id answered the probe' };\n }\n if (input.promptChars > CODEX_EXEC_PROMPT_CEILING_CHARS) {\n return {\n mode: 'claude',\n reason: 'prompt is ' +\n input.promptChars +\n ' chars, over the ' +\n CODEX_EXEC_PROMPT_CEILING_CHARS +\n '-char codex exec ceiling (it would stall)',\n };\n }\n return { mode: 'exec', reason: 'codex exec on ' + input.probedId };\n}",
|
|
86
|
+
},
|
|
87
|
+
"challenge-panel": {
|
|
88
|
+
name: "challenge-panel",
|
|
89
|
+
version: "1.0.0",
|
|
90
|
+
contentHash: "a015a5f45933352a3a2508fc35fb8df09d6b403566b4e33738904c52f3fa07fc",
|
|
91
|
+
sourcePath: "packages/@dzhechkov/harness-core/src/challenge-panel.ts",
|
|
92
|
+
requires: [],
|
|
93
|
+
exports: ["CHALLENGE_QUESTIONS","CHALLENGE_VERDICT_SCHEMA","buildChallengeBrief","sanitizeFinding","sanitizeVerdict","findingsNeedingCrossValidation","confirmedVerdict","renderVerdict","pickAdversaryModel","classifyModelFamily"],
|
|
94
|
+
code: "const SEV_RANK = { P0: 3, P1: 2, P2: 1 };\nconst VALID_CID = new Set(['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8']);\nconst isValidSeverity = (s) => s === 'P0' || s === 'P1' || s === 'P2';\nconst CHALLENGE_QUESTIONS = Object.freeze([\n Object.freeze({\n id: 'C1',\n title: 'Architecture anti-cement',\n prompt: 'Does this plan cement a NEW bad pattern, a wrong boundary, or a shortcut that later work will be ' +\n 'forced to copy? Check it against the product map + vision. IMPORTANT: deviating from a pattern that ' +\n 'is REGISTERED in the accepted-degradations registry is NOT a finding — that debt is already owned. ' +\n 'A genuinely new degradation the plan introduces → name it and propose it for the registry.',\n }),\n Object.freeze({\n id: 'C2',\n title: 'Production-ready',\n prompt: 'Where would this fall over in production? Missing error handling, unhandled failure modes, ' +\n 'resource leaks, missing observability, config/secret handling, migration/rollback. Name the ' +\n 'concrete input or condition that breaks it, not a general worry.',\n }),\n Object.freeze({\n id: 'C3',\n title: 'Test sufficiency + honesty (both ways)',\n prompt: 'Attack the test plan from BOTH sides. Under-testing: which claim — especially a safety property the ' +\n 'ADR NAMES (\"never X\") — has no falsifying test? A test that cannot fail (a wrapper, a tautology, ' +\n 'asserting the mock) is not coverage. Over-testing: which tests are theater — restating the ' +\n 'implementation, testing the framework, brittle snapshots that verify nothing a user cares about?',\n }),\n Object.freeze({\n id: 'C4',\n title: 'Overengineering sweep',\n prompt: 'What in this plan is built for a requirement nobody stated? Speculative generality, an abstraction ' +\n 'with one caller, a config knob no one asked for, a plugin seam for a single case. For each: what is ' +\n 'the simpler thing that meets the ACTUAL requirement?',\n }),\n Object.freeze({\n id: 'C5',\n title: 'Silent decisions',\n prompt: 'Which load-bearing decisions did the plan make WITHOUT surfacing them as a decision? A default that ' +\n 'is really a policy, a chosen tradeoff presented as the only option, a dependency added in passing. ' +\n 'Each silent decision the owner did not get to refuse is a finding.',\n }),\n Object.freeze({\n id: 'C6',\n title: 'Runtime consistency',\n prompt: 'Will this behave consistently with how the rest of the system already works — same error shape, ' +\n 'same config source, same module/ESM conventions, same logging, same naming? Point to the specific ' +\n 'existing convention the plan contradicts.',\n }),\n Object.freeze({\n id: 'C7',\n title: 'Scope',\n prompt: 'Is the plan more than ~1.5× the size the request actually needs? If so, what is the concrete cut ' +\n 'list — which files/steps/abstractions to drop to hit the real requirement — and what is genuinely ' +\n 'load-bearing and must stay?',\n }),\n Object.freeze({\n id: 'C8',\n title: 'Executability',\n prompt: 'Could an executor who is NOT the plan author complete every step without coming back to ask what was ' +\n 'meant? Find the steps that are under-specified, assume unstated context, or hide a research task ' +\n 'behind an imperative verb (\"integrate X\", \"wire up Y\") with no concrete how.',\n }),\n]);\nconst CHALLENGE_VERDICT_SCHEMA = Object.freeze({\n type: 'object',\n required: ['findings', 'summary'],\n properties: {\n findings: {\n type: 'array',\n items: {\n type: 'object',\n required: ['c', 'severity', 'title', 'why'],\n properties: {\n c: { type: 'string', enum: ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8'] },\n severity: { type: 'string', enum: ['P0', 'P1', 'P2'] },\n title: { type: 'string' },\n why: { type: 'string', description: 'concrete failing input/condition, not a general worry' },\n where: { type: 'string', description: 'plan section / file:line if locatable' },\n },\n },\n },\n summary: { type: 'string' },\n },\n});\nconst HR = '─'.repeat(72);\nconst section = (title, body) => body === undefined ? `## ${title}\\n(not provided — panel runs with less calibration)\\n` : `## ${title}\\n${body}\\n`;\nfunction buildChallengeBrief(ctx) {\n const lines = [];\n lines.push('# CHALLENGE PANEL — adversarial plan-gate');\n lines.push('');\n lines.push('You are a FRESH adversarial reviewer. You did NOT write this plan. Your job is to BREAK it, not to ' +\n 'confirm it — find the concrete way each answer fails, or state plainly that you could not. A finding ' +\n 'is a specific failing input/condition/omission, never a general worry. Every P0/P1 you raise will be ' +\n 'independently cross-validated, so do not pad — theory that cannot be reproduced will be dropped.');\n lines.push('');\n lines.push(HR);\n lines.push(section('PLAN UNDER REVIEW (' + ctx.planPath + ')', ctx.plan === '' ? undefined : ctx.plan));\n lines.push(section('PRODUCT VISION (boundaries + principles)', ctx.vision));\n lines.push(section('TESTING POLICY (what \"done\" + honest tests mean here)', ctx.testing));\n lines.push(section('PRODUCT MAP (subsystems + existing conventions)', ctx.map));\n lines.push(section('ACCEPTED-DEGRADATIONS REGISTRY (deviating from THESE is NOT a finding)', ctx.degradations));\n lines.push(section('CODE HINTS', ctx.codeHints));\n lines.push(HR);\n lines.push('');\n lines.push('## Ask each question in \"break it, don\\'t confirm it\" mode');\n for (const q of CHALLENGE_QUESTIONS) {\n lines.push('');\n lines.push(`### ${q.id} — ${q.title}`);\n lines.push(q.prompt);\n }\n lines.push('');\n lines.push(HR);\n lines.push('## Output');\n lines.push('Return findings tagged by C-number with severity P0 (would ship a serious defect / cements bad ' +\n 'architecture), P1 (real gap, fix before code), or P2 (worth noting). For each: a concrete `why` ' +\n '(the failing input/condition) and `where` if locatable. Then a one-paragraph `summary`. This gate ' +\n 'ADVISES — it does not block; the owner decides.');\n lines.push('');\n lines.push('Verdict JSON schema: ' + JSON.stringify(CHALLENGE_VERDICT_SCHEMA));\n return lines.join('\\n');\n}\nfunction sanitizeFinding(raw) {\n if (raw === null || typeof raw !== 'object')\n return null;\n const r = raw;\n if (typeof r.c !== 'string' || !VALID_CID.has(r.c))\n return null;\n if (!isValidSeverity(r.severity))\n return null;\n if (typeof r.title !== 'string' || r.title === '')\n return null;\n if (typeof r.why !== 'string' || r.why === '')\n return null;\n const out = { c: r.c, severity: r.severity, title: r.title, why: r.why };\n return typeof r.where === 'string' && r.where !== '' ? { ...out, where: r.where } : out;\n}\nfunction sanitizeVerdict(raw) {\n if (raw === null || typeof raw !== 'object')\n return null;\n const r = raw;\n if (!Array.isArray(r.findings))\n return null;\n const findings = r.findings.map(sanitizeFinding).filter((f) => f !== null);\n return { findings, summary: typeof r.summary === 'string' ? r.summary : '' };\n}\nfunction findingsNeedingCrossValidation(v) {\n return [...v.findings]\n .filter((f) => f.severity === 'P0' || f.severity === 'P1')\n .sort(cmpFinding);\n}\nfunction cmpFinding(a, b) {\n const s = SEV_RANK[b.severity] - SEV_RANK[a.severity];\n if (s !== 0)\n return s;\n if (a.c !== b.c)\n return a.c < b.c ? -1 : 1;\n return a.title < b.title ? -1 : a.title > b.title ? 1 : 0;\n}\nfunction confirmedVerdict(v, realFlags) {\n const need = findingsNeedingCrossValidation(v);\n const kept = v.findings.filter((f) => f.severity === 'P2');\n need.forEach((f, i) => {\n if (realFlags[i] === true)\n kept.push({ ...f, crossValidated: true });\n });\n kept.sort(cmpFinding);\n return { findings: kept, summary: v.summary };\n}\nfunction renderVerdict(v) {\n if (v.findings.length === 0) {\n return `Challenge panel: no cross-validated findings. ${v.summary}`.trim();\n }\n const byId = (s) => v.findings.filter((f) => f.severity === s).sort(cmpFinding);\n const out = ['Challenge panel verdict (advisory — you decide):', ''];\n for (const sev of ['P0', 'P1', 'P2']) {\n const group = byId(sev);\n if (group.length === 0)\n continue;\n out.push(`### ${sev} (${group.length})`);\n for (const f of group) {\n const cv = f.crossValidated ? ' ✓cross-validated' : '';\n const where = f.where ? ` [${f.where}]` : '';\n out.push(`- ${f.c} ${f.title}${where}${cv}`);\n out.push(` why: ${f.why}`);\n }\n out.push('');\n }\n out.push(v.summary);\n return out.join('\\n').trim();\n}\nfunction pickAdversaryModel(plannerModel) {\n const fam = classifyModelFamily(plannerModel);\n if (fam === 'claude')\n return { model: 'codex', note: `plan authored on ${plannerModel} (Claude) → Codex adversary (cross-family)` };\n if (fam === 'openai')\n return { model: 'claude', note: `plan authored on ${plannerModel} (OpenAI/Codex) → fresh Claude adversary (cross-family)` };\n return { model: 'claude', note: `plan author family UNKNOWN (${plannerModel}) → Claude adversary by default; verify it is cross-family before trusting the verdict` };\n}\nfunction classifyModelFamily(model) {\n const m = String(model).toLowerCase();\n if (/claude|opus|sonnet|haiku|fable/.test(m))\n return 'claude';\n if (/codex|gpt|openai|\\bo[1-9]\\b/.test(m))\n return 'openai';\n return 'unknown';\n}",
|
|
95
|
+
},
|
|
96
|
+
"trace": {
|
|
97
|
+
name: "trace",
|
|
98
|
+
version: "1.1.0",
|
|
99
|
+
contentHash: "fc59098bb0deb747c4c1c9a6ba843df70edfe0dc5ef8370c0f7192afc7657890",
|
|
100
|
+
sourcePath: "packages/@dzhechkov/harness-core/src/loop-trace.ts",
|
|
101
|
+
requires: [],
|
|
102
|
+
exports: ["LOOP_TRACE_SCHEMA_VERSION","TRACE_RUNID_RE","TRACE_KEY_RE","traceShellQuote","traceValidateEvent","traceInit","traceOnDispatch","traceOnSettle","traceClose","traceFlushCmd","traceFaRecordCmd","traceLedgerLine","traceLedgerAppendCmd"],
|
|
103
|
+
code: "const LOOP_TRACE_SCHEMA_VERSION = 1;\nconst TRACE_RUNID_RE = /^[a-z0-9-]{1,40}$/;\nconst TRACE_KEY_RE = /^[a-z0-9_.:-]{1,64}$/i;\nfunction traceShellQuote(s) {\n return \"'\" + String(s).replace(/'/g, \"'\\\\''\") + \"'\";\n}\nfunction traceValidateEvent(e) {\n if (typeof e !== 'object' || e === null || Array.isArray(e))\n return 'event must be an object';\n const ev = e;\n if (ev['v'] !== 1)\n return 'v must be 1';\n if (typeof ev['runId'] !== 'string' || !TRACE_RUNID_RE.test(ev['runId']))\n return 'runId fails its VO regex';\n if (typeof ev['seq'] !== 'number' || !Number.isInteger(ev['seq']) || ev['seq'] < 1)\n return 'seq must be a positive integer';\n const kind = ev['event'];\n if (kind === 'dispatched') {\n if (typeof ev['invocationId'] !== 'string' || ev['invocationId'] === '')\n return 'invocationId required';\n if (typeof ev['stepId'] !== 'string' || !TRACE_KEY_RE.test(ev['stepId']))\n return 'stepId fails its VO regex';\n if (ev['itemKey'] !== null && (typeof ev['itemKey'] !== 'string' || !TRACE_KEY_RE.test(ev['itemKey'])))\n return 'itemKey fails its VO regex';\n if (typeof ev['attempt'] !== 'number' || ev['attempt'] < 1)\n return 'attempt must be >= 1';\n if (typeof ev['phase'] !== 'string' || ev['phase'] === '')\n return 'phase required';\n if (!Array.isArray(ev['causedBy']) || ev['causedBy'].some((n) => typeof n !== 'number'))\n return 'causedBy must be a number array';\n return null;\n }\n if (kind === 'settled') {\n if (typeof ev['invocationId'] !== 'string' || ev['invocationId'] === '')\n return 'invocationId required';\n if (ev['outcome'] !== 'ok' && ev['outcome'] !== 'null' && ev['outcome'] !== 'error')\n return 'outcome must be ok|null|error';\n return null;\n }\n if (kind === 'run.opened') {\n if (typeof ev['planDigest'] !== 'string' || typeof ev['execFp'] !== 'string')\n return 'run.opened needs planDigest + execFp';\n return null;\n }\n if (kind === 'run.closed') {\n const c = ev['counts'];\n if (typeof c !== 'object' || c === null)\n return 'run.closed needs counts';\n return null;\n }\n return 'unknown event kind';\n}\nfunction traceInit(runId, planDigest, execFp) {\n if (!TRACE_RUNID_RE.test(runId))\n throw new Error('loop-trace: runId fails ' + String(TRACE_RUNID_RE));\n const state = { runId, seq: 0, dispatched: 0, settled: 0, buffer: [] };\n const opened = { v: 1, runId, seq: ++state.seq, event: 'run.opened', planDigest, execFp };\n traceBuffer(state, opened);\n return state;\n}\nfunction traceBuffer(state, e) {\n const err = traceValidateEvent(e);\n if (err !== null)\n throw new Error('loop-trace: refusing non-conforming event (' + err + ') — the authoritative ordering source is never repaired later');\n state.buffer.push(JSON.stringify(e));\n}\nfunction traceOnDispatch(state, e) {\n const seq = ++state.seq;\n state.dispatched++;\n traceBuffer(state, {\n v: 1,\n runId: state.runId,\n seq,\n event: 'dispatched',\n invocationId: e.invocationId,\n stepId: e.stepId,\n itemKey: e.itemKey,\n attempt: e.attempt,\n phase: e.phase,\n model: e.model,\n causedBy: e.causedBy,\n });\n return seq;\n}\nfunction traceOnSettle(state, e) {\n const seq = ++state.seq;\n state.settled++;\n traceBuffer(state, { v: 1, runId: state.runId, seq, event: 'settled', invocationId: e.invocationId, outcome: e.outcome });\n return seq;\n}\nfunction traceClose(state) {\n const closed = {\n v: 1,\n runId: state.runId,\n seq: ++state.seq,\n event: 'run.closed',\n counts: { dispatched: state.dispatched, settled: state.settled },\n };\n traceBuffer(state, closed);\n}\nfunction traceFlushCmd(state, traceFileAbs) {\n if (state.buffer.length === 0)\n return null;\n const lines = state.buffer.splice(0, state.buffer.length);\n const file = traceShellQuote(traceFileAbs);\n const dir = traceShellQuote(traceFileAbs.replace(/\\/[^/]*$/, ''));\n const printfs = lines\n .map((l) => \"printf '%s\\\\n' \" + traceShellQuote(l) + ' | sed \"s/}$/,\\\\\"wallTime\\\\\":\\\\\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\\\\\"}/\" >> ' + file)\n .join(' && ');\n return 'mkdir -p ' + dir + ' && ' + printfs;\n}\nfunction traceFaRecordCmd(dzBin, slug, stepLabel, projectAbs) {\n if (typeof slug !== 'string' || slug === ''\n || typeof stepLabel !== 'string' || stepLabel === ''\n || typeof projectAbs !== 'string' || projectAbs === '')\n return null;\n const bin = typeof dzBin === 'string' && dzBin !== '' ? dzBin : 'dz';\n const cmd = traceShellQuote(bin) + ' statusline --fa-record --slug ' + traceShellQuote(slug)\n + ' --step ' + traceShellQuote(stepLabel) + ' --kind loop --project ' + traceShellQuote(projectAbs);\n return cmd + ' >/dev/null 2>&1';\n}\nfunction traceLedgerLine(opts) {\n try {\n if (typeof opts.slug !== 'string' || opts.slug === '')\n return null;\n const agents = typeof opts.agents === 'number'\n && Number.isFinite(opts.agents)\n && Number.isInteger(opts.agents)\n && opts.agents >= 0\n ? opts.agents\n : 0;\n const date = typeof opts.date === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(opts.date) ? opts.date : null;\n const outcome = typeof opts.outcome === 'string' && opts.outcome !== '' ? opts.outcome : 'unknown';\n const line = JSON.stringify({\n slug: opts.slug,\n stage: 'loop-run',\n tier: null,\n tokens: null,\n minutes: null,\n agents,\n coder: null,\n grade: null,\n date,\n auto: true,\n outcome,\n runId: typeof opts.runId === 'string' ? opts.runId : null,\n planDigest: typeof opts.planDigest === 'string' ? opts.planDigest : null,\n });\n return line.length <= 4000 ? line : null;\n }\n catch {\n return null;\n }\n}\nfunction traceLedgerAppendCmd(repoAbs, line) {\n if (typeof repoAbs !== 'string' || repoAbs === '' || typeof line !== 'string' || line === '')\n return null;\n const dir = traceShellQuote(repoAbs + '/.dz/feature-adr');\n const file = traceShellQuote(repoAbs + '/.dz/feature-adr/run-cost-ledger.jsonl');\n return 'mkdir -p ' + dir\n + \" && printf '%s' \" + traceShellQuote(line)\n + ' | sed \"s/\\\\\"date\\\\\":null/\\\\\"date\\\\\":\\\\\"$(date -u +%Y-%m-%d)\\\\\"/\" >> ' + file\n + \" && printf '\\\\n' >> \" + file\n + ' && echo LEDGER-OK';\n}\nfunction invocations(run) {\n const out = new Map();\n for (const e of run.events) {\n if (e.event === 'dispatched') {\n out.set(e.invocationId, {\n invocationId: e.invocationId,\n stepId: e.stepId,\n itemKey: e.itemKey,\n dispatchSeq: e.seq,\n settleSeq: null,\n causedBy: e.causedBy,\n });\n }\n else if (e.event === 'settled') {\n const inv = out.get(e.invocationId);\n if (inv)\n inv.settleSeq = e.seq;\n }\n }\n return [...out.values()];\n}",
|
|
104
|
+
},
|
|
105
|
+
"ha-consult-router": {
|
|
106
|
+
name: "ha-consult-router",
|
|
107
|
+
version: "1.0.0",
|
|
108
|
+
contentHash: "1df9e5582f8ec888cf2d8c0f38a6dac18c970489addbb1bfd203949f3e343195",
|
|
109
|
+
sourcePath: ".claude/workflows/lib/ha-consult-router.mjs",
|
|
110
|
+
requires: [],
|
|
111
|
+
exports: ["CONSULT_PHASES","activeMedications","interactionFlaggedFor","anyMedIndicationOverlaps","reconciliationUncertain","polypharmacyIsRelevant","distinctClinicalSpecialties","hasCrossDepartmentContradiction","abnormalAnalyteDepartments","TEAM_CRITERIA","shouldRouteTeam"],
|
|
112
|
+
code: "const CONSULT_PHASES = Object.freeze([\n 'ESCALATED_IMMEDIATE', // terminal — ambulance hit, zero specialist agents (INV-10)\n 'ESCALATED_URGENT', // non-terminal — doctor_24h hit: banner at position 0, rides every later payload (INV-10b)\n 'ROUTED_RESTRICTED', // terminal — pregnant / child → referral out\n 'ROUTED_SOLO', // terminal — today's solo flow, untouched\n 'AWAITING_TRIAGE_RESPONSE', // pause 1 of exactly 2\n 'AWAITING_SYNTHESIS_APPROVAL',// pause 2 of exactly 2\n 'FAILED_GATE', // terminal — enforce gate failed twice, loud\n 'COMPLETED', // terminal\n]);\n\nfunction activeMedications(profile) {\n return ((profile && profile.medications) || []).filter((m) => m && m.active !== false);\n}\n\nfunction interactionFlaggedFor(complaint) {\n return (((complaint && complaint.interaction_flags) || []).length > 0);\n}\n\nfunction anyMedIndicationOverlaps(profile, complaint) {\n const systems = new Set(((complaint && complaint.systems) || []).map(String));\n return activeMedications(profile).some((m) => ((m.indication_systems || []).some((s) => systems.has(String(s)))));\n}\n\nfunction reconciliationUncertain(profile) {\n return Boolean(profile) && profile.med_list_reconciled === false;\n}\n\nfunction polypharmacyIsRelevant(profile, complaint) {\n if (activeMedications(profile).length < 5) return false;\n return interactionFlaggedFor(complaint)\n || anyMedIndicationOverlaps(profile, complaint)\n || reconciliationUncertain(profile);\n}\n\nfunction distinctClinicalSpecialties(complaintMap) {\n return new Set(((complaintMap && complaintMap.specialties) || []).map(String)).size;\n}\n\nfunction hasCrossDepartmentContradiction(profile) {\n return (((profile && profile.contradictions) || []).length > 0);\n}\n\nfunction abnormalAnalyteDepartments(profile) {\n return new Set(((profile && profile.abnormal_departments) || []).map(String)).size;\n}\n\nconst TEAM_CRITERIA = Object.freeze(['multi_system', 'contradiction', 'relevant_polypharmacy', 'volume']);\n\nfunction shouldRouteTeam(complaintMap, profile, complaint) {\n const teamCriteria = [];\n if (distinctClinicalSpecialties(complaintMap) >= 2) teamCriteria.push('multi_system');\n if (hasCrossDepartmentContradiction(profile)) teamCriteria.push('contradiction');\n if (polypharmacyIsRelevant(profile, complaint)) teamCriteria.push('relevant_polypharmacy');\n if (abnormalAnalyteDepartments(profile) >= 3) teamCriteria.push('volume');\n // The acknowledged FALSE NEGATIVE (high-stakes single-specialty question) is handled at\n // checkpoint A — the user can force a team; it is named in the checkpoint banner, not patched\n // with a vaguer criterion (that would make routing subjective and circular, B2).\n return { route: teamCriteria.length > 0 ? 'team' : 'solo', criteria: teamCriteria };\n}",
|
|
113
|
+
},
|
|
114
|
+
};
|