@nusoft/nuos-build-catalogue 0.33.3 → 0.36.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/README.md +3 -3
- package/dist/cli.js +48 -0
- package/dist/commands/end-of-session.js +67 -14
- package/dist/commands/state-compile.d.ts +108 -0
- package/dist/commands/state-compile.js +793 -0
- package/dist/embedder/ollama.d.ts +7 -0
- package/dist/embedder/ollama.js +27 -1
- package/package.json +5 -4
- package/scripts/hooks/pre-commit +43 -0
- package/templates/hooks/pre-commit +43 -0
- package/templates/starter-kit/methodfile.json +7 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ The embedder is selected via `NUOS_CATALOGUE_EMBEDDER`:
|
|
|
14
14
|
|
|
15
15
|
| Value | Provider | Default model | Dimensions | Notes |
|
|
16
16
|
|---|---|---|---|---|
|
|
17
|
-
| `ollama` (default) | Local Ollama | `qwen3-embedding:
|
|
17
|
+
| `ollama` (default) | Local Ollama | `qwen3-embedding:0.6b` | 1024 | **Sovereignty by default.** No network egress. The 0.6b default (~600 MB) runs on any modern laptop, including CPU-only. For better recall on a machine with headroom, raise fidelity with `NUOS_CATALOGUE_OLLAMA_MODEL=qwen3-embedding:4b` (2560 dims, ~2.5 GB) or `qwen3-embedding:8b` (4096 dims, ~4.7 GB). Needs `ollama serve` running and the model pulled (`ollama pull qwen3-embedding:0.6b`). |
|
|
18
18
|
| `vertex` | Google Vertex | `text-embedding-005` | 768 | Cloud Google. Needs `GOOGLE_CLOUD_PROJECT` plus a Vertex access token (set `GOOGLE_VERTEX_ACCESS_TOKEN`, or have `gcloud` on PATH and run `gcloud auth application-default login`). |
|
|
19
19
|
| `openai` | OpenAI | `text-embedding-3-small` | 1536 | Cloud OpenAI. Needs `OPENAI_API_KEY`. |
|
|
20
20
|
| `stub` | Hash-based, no API | — | 384 | Tests + dev only. Results are noisy. |
|
|
@@ -26,9 +26,9 @@ Switching embedder (or model variant) requires a full reindex (`rm -rf .nuos-cat
|
|
|
26
26
|
```bash
|
|
27
27
|
# Pre-flight (one time):
|
|
28
28
|
ollama serve # in another shell
|
|
29
|
-
ollama pull qwen3-embedding:
|
|
29
|
+
ollama pull qwen3-embedding:0.6b # ~600 MB download
|
|
30
30
|
|
|
31
|
-
# Index the catalogue (first time
|
|
31
|
+
# Index the catalogue (first time re-embeds everything; later runs only re-embed changed files)
|
|
32
32
|
npm run index
|
|
33
33
|
|
|
34
34
|
# Search
|
package/dist/cli.js
CHANGED
|
@@ -436,6 +436,15 @@ Usage:
|
|
|
436
436
|
nuos-catalogue memory store --value="..." [--wu=wu-007] [--agent=architect] [--key="label"]
|
|
437
437
|
nuos-catalogue memory search --query="..." [--limit=N] [--wu=wu-007] [--agent=architect]
|
|
438
438
|
|
|
439
|
+
nuos-catalogue state compile [--dry-run] [--state-md=<path>]
|
|
440
|
+
(WU 113b — recompile STATE.md generated regions from canonical store;
|
|
441
|
+
splices metadata / what-is-next / open-questions / decisions / risks /
|
|
442
|
+
health-check regions; preserves authored prose byte-for-byte)
|
|
443
|
+
nuos-catalogue state drift-check [--state-md=<path>]
|
|
444
|
+
(WU 113b Stage B — check whether STATE.md generated regions match
|
|
445
|
+
canonical state; exit 0 on clean / no-regions / can't-run;
|
|
446
|
+
exit 1 ONLY on confirmed generated-region drift; called by pre-commit hook)
|
|
447
|
+
|
|
439
448
|
nuos-catalogue end-of-session
|
|
440
449
|
(WU 112 — verify-and-gate: checks the nine end-of-session protocol steps
|
|
441
450
|
against disk facts; prints a per-check report; exits non-zero on a blocked
|
|
@@ -657,6 +666,45 @@ async function main() {
|
|
|
657
666
|
process.exit(result.exitCode);
|
|
658
667
|
break;
|
|
659
668
|
}
|
|
669
|
+
case 'state': {
|
|
670
|
+
// `state compile` — regenerate the generated regions of STATE.md (WU 113b / D132).
|
|
671
|
+
// `state drift-check` — check for generated-region drift (Stage B; called by pre-commit hook).
|
|
672
|
+
const sub = args.positional[0];
|
|
673
|
+
const buildRoot = resolveBuildRoot(args.flags['build-root']);
|
|
674
|
+
const workflowsPath = resolveWorkflowsPath(buildRoot, args.flags['workflows']);
|
|
675
|
+
if (sub === 'compile') {
|
|
676
|
+
const { cmdStateCompile } = await import('./commands/state-compile.js');
|
|
677
|
+
const store = await openWorkflowStore(workflowsPath);
|
|
678
|
+
const result = await cmdStateCompile(store, {
|
|
679
|
+
buildRoot,
|
|
680
|
+
stateMdPath: args.flags['state-md'] ? String(args.flags['state-md']) : undefined,
|
|
681
|
+
dryRun: Boolean(args.flags['dry-run']),
|
|
682
|
+
});
|
|
683
|
+
if (result.output)
|
|
684
|
+
console.log(result.output);
|
|
685
|
+
process.exit(result.exitCode);
|
|
686
|
+
}
|
|
687
|
+
else if (sub === 'drift-check') {
|
|
688
|
+
const { cmdStateDriftCheck } = await import('./commands/state-compile.js');
|
|
689
|
+
const store = await openWorkflowStore(workflowsPath);
|
|
690
|
+
const result = await cmdStateDriftCheck(store, {
|
|
691
|
+
buildRoot,
|
|
692
|
+
stateMdPath: args.flags['state-md'] ? String(args.flags['state-md']) : undefined,
|
|
693
|
+
});
|
|
694
|
+
// Drift-check output: clean/skipped messages go to stderr (informational); drifted goes to stderr too.
|
|
695
|
+
if (result.output)
|
|
696
|
+
process.stderr.write(result.output + '\n');
|
|
697
|
+
process.exit(result.exitCode);
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
console.error(`unknown state subcommand: ${sub ?? '(none)'}`);
|
|
701
|
+
console.error('available:');
|
|
702
|
+
console.error(' state compile [--dry-run] [--state-md=<path>] [--build-root=<dir>] [--workflows=<file>]');
|
|
703
|
+
console.error(' state drift-check [--state-md=<path>] [--build-root=<dir>] [--workflows=<file>]');
|
|
704
|
+
process.exit(1);
|
|
705
|
+
}
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
660
708
|
case 'start-of-session': {
|
|
661
709
|
// Reserved handle — body in a follow-up WU.
|
|
662
710
|
console.error('start-of-session: not yet implemented (WU 112 reserves the handle; body in a follow-up WU).');
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { stat, readdir, readFile } from 'node:fs/promises';
|
|
24
24
|
import path from 'node:path';
|
|
25
|
+
import { cmdStateCompile } from './state-compile.js';
|
|
25
26
|
const BUILD_MAINTAINER = {
|
|
26
27
|
kind: 'staff',
|
|
27
28
|
id: 'build-maintainer',
|
|
@@ -46,7 +47,7 @@ export async function cmdEndOfSession(store, runtime, args) {
|
|
|
46
47
|
}
|
|
47
48
|
// Gather disk facts — this is the only place filesystem access happens
|
|
48
49
|
// (the workflow itself is pure).
|
|
49
|
-
const catalogueFacts = await gatherFacts(args.buildRoot, activeWuHandle, sessionStartIso, today);
|
|
50
|
+
const catalogueFacts = await gatherFacts(args.buildRoot, activeWuHandle, sessionStartIso, today, store);
|
|
50
51
|
// Check for an existing (incomplete) session.end:<date> record.
|
|
51
52
|
const existingHandle = `session.end:${today}`;
|
|
52
53
|
const existingRecord = store.get(existingHandle);
|
|
@@ -71,6 +72,7 @@ export async function cmdEndOfSession(store, runtime, args) {
|
|
|
71
72
|
'capture_open_questions',
|
|
72
73
|
'capture_risks',
|
|
73
74
|
'update_work_units_index',
|
|
75
|
+
'recompile_state_md',
|
|
74
76
|
'update_state_md',
|
|
75
77
|
'write_session_log',
|
|
76
78
|
'confirm_no_loss',
|
|
@@ -127,7 +129,7 @@ export async function cmdEndOfSession(store, runtime, args) {
|
|
|
127
129
|
// ---------------------------------------------------------------------------
|
|
128
130
|
// Disk fact gathering — the only place fs access happens
|
|
129
131
|
// ---------------------------------------------------------------------------
|
|
130
|
-
async function gatherFacts(buildRoot, activeWuHandle, sessionStartIso, sessionDate) {
|
|
132
|
+
async function gatherFacts(buildRoot, activeWuHandle, sessionStartIso, sessionDate, store) {
|
|
131
133
|
const sessionStartMs = new Date(sessionStartIso).getTime();
|
|
132
134
|
// Step 1: WU notes
|
|
133
135
|
const { wuNotesTouched, wuNotesHasTodayHeading } = await checkWuNotes(buildRoot, activeWuHandle, sessionStartMs, sessionDate);
|
|
@@ -137,6 +139,10 @@ async function gatherFacts(buildRoot, activeWuHandle, sessionStartIso, sessionDa
|
|
|
137
139
|
const risksParity = await checkRisksParity(buildRoot);
|
|
138
140
|
// Step 5: work-units index
|
|
139
141
|
const doneMoveOk = await checkWorkUnitsIndex(buildRoot);
|
|
142
|
+
// Step 5.5 (D132): recompile the generated regions of STATE.md.
|
|
143
|
+
// This is the orchestrate-and-write step sanctioned by D132 for generated regions.
|
|
144
|
+
// It must not fail the session if STATE.md has no sentinel regions yet (pre-cutover).
|
|
145
|
+
const { stateMdRecompileResult, stateMdRecompileDetail } = await recompileStateMd(buildRoot, store);
|
|
140
146
|
// Step 6: STATE.md
|
|
141
147
|
const { stateMdTouched, stateMdLastUpdated, stateMdLastSessionResolves } = await checkStateMd(buildRoot, sessionStartMs, sessionDate);
|
|
142
148
|
// Step 7: session log
|
|
@@ -148,6 +154,8 @@ async function gatherFacts(buildRoot, activeWuHandle, sessionStartIso, sessionDa
|
|
|
148
154
|
questionsParity,
|
|
149
155
|
risksParity,
|
|
150
156
|
doneMoveOk,
|
|
157
|
+
stateMdRecompileResult,
|
|
158
|
+
stateMdRecompileDetail,
|
|
151
159
|
stateMdTouched,
|
|
152
160
|
stateMdLastUpdated,
|
|
153
161
|
stateMdLastSessionResolves,
|
|
@@ -294,6 +302,44 @@ async function checkRisksParity(buildRoot) {
|
|
|
294
302
|
// This check is present for forward-compat when risks get individual files.
|
|
295
303
|
return { filesWithoutRow: [], rowsWithoutFile: [] };
|
|
296
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Recompile the generated regions of STATE.md (D132 / D130: orchestrate-and-write
|
|
307
|
+
* for the generated regions is sanctioned by D132; authored prose is never touched).
|
|
308
|
+
*
|
|
309
|
+
* Fail-open contract (same as `cmdStateDriftCheck`):
|
|
310
|
+
* - 'skipped' when STATE.md has no sentinel regions yet (pre-cutover) — ok
|
|
311
|
+
* - 'ok' when the recompile succeeded (or was already current)
|
|
312
|
+
* - 'error' when the compile command returned non-zero (adapter error, splice error)
|
|
313
|
+
*
|
|
314
|
+
* A 'skipped' result is treated as passing by the pack workflow so that
|
|
315
|
+
* end-of-session is not broken for catalogues that haven't completed Stage B cutover.
|
|
316
|
+
*/
|
|
317
|
+
async function recompileStateMd(buildRoot, store) {
|
|
318
|
+
try {
|
|
319
|
+
const result = await cmdStateCompile(store, { buildRoot });
|
|
320
|
+
if (result.exitCode === 0) {
|
|
321
|
+
return { stateMdRecompileResult: 'ok', stateMdRecompileDetail: result.output?.trim() };
|
|
322
|
+
}
|
|
323
|
+
// Non-zero exit from cmdStateCompile — check if it's the missing-sentinel case (pre-cutover).
|
|
324
|
+
// The missing-sentinel output contains the specific wording from the command.
|
|
325
|
+
if (result.output?.includes('sentinel regions are absent')) {
|
|
326
|
+
return {
|
|
327
|
+
stateMdRecompileResult: 'skipped',
|
|
328
|
+
stateMdRecompileDetail: 'sentinel regions absent — pre-cutover',
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
stateMdRecompileResult: 'error',
|
|
333
|
+
stateMdRecompileDetail: result.output?.trim(),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
return {
|
|
338
|
+
stateMdRecompileResult: 'error',
|
|
339
|
+
stateMdRecompileDetail: err instanceof Error ? err.message : String(err),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
297
343
|
async function checkWorkUnitsIndex(buildRoot) {
|
|
298
344
|
const indexPath = path.join(buildRoot, 'work-units', '_index.md');
|
|
299
345
|
const content = await fileContent(indexPath);
|
|
@@ -349,7 +395,9 @@ async function checkStateMd(buildRoot, sessionStartMs, sessionDate) {
|
|
|
349
395
|
const stateMdTouched = mtime ? mtime.getTime() > sessionStartMs : false;
|
|
350
396
|
const content = await fileContent(stateMdPath);
|
|
351
397
|
let stateMdLastUpdated = '';
|
|
352
|
-
|
|
398
|
+
// Renamed from stateMdLastSessionResolves → stateMdLastSessionPresent (WU 113b).
|
|
399
|
+
// The field checks presence of a non-empty "Last session" row, not link resolution.
|
|
400
|
+
let stateMdLastSessionPresent = false;
|
|
353
401
|
if (content) {
|
|
354
402
|
// Fix 1 (WU 112 fix-pass): accept all three "Last updated" shapes:
|
|
355
403
|
// table-row: | Last updated | 2026-05-31 (**Session 115 — ...**) ... |
|
|
@@ -370,10 +418,14 @@ async function checkStateMd(buildRoot, sessionStartMs, sessionDate) {
|
|
|
370
418
|
if (sessionLineMatch) {
|
|
371
419
|
// The row is non-empty if it contains more than just the label itself.
|
|
372
420
|
const rowText = sessionLineMatch[0].replace(/Last session/i, '').replace(/[|:\s]/g, '');
|
|
373
|
-
|
|
421
|
+
stateMdLastSessionPresent = rowText.length > 0;
|
|
374
422
|
}
|
|
375
423
|
}
|
|
376
|
-
|
|
424
|
+
// Return under the pack's EndOfSessionFacts field name (stateMdLastSessionResolves)
|
|
425
|
+
// — the internal variable was renamed to stateMdLastSessionPresent above to clarify
|
|
426
|
+
// the semantics (presence check, not link-resolution). The published interface is
|
|
427
|
+
// unchanged so the pack type is not broken.
|
|
428
|
+
return { stateMdTouched, stateMdLastUpdated, stateMdLastSessionResolves: stateMdLastSessionPresent };
|
|
377
429
|
}
|
|
378
430
|
async function checkSessionLog(buildRoot, sessionDate) {
|
|
379
431
|
const sessionsDir = path.join(buildRoot, 'sessions');
|
|
@@ -406,15 +458,16 @@ function formatReport(payload, today, resumedFrom, dryRun) {
|
|
|
406
458
|
lines.push('══════════════════════════════════════════════════════════════════════');
|
|
407
459
|
lines.push('');
|
|
408
460
|
const STEP_LABELS = {
|
|
409
|
-
update_active_wu_notes: 'Step 1
|
|
410
|
-
capture_decisions: 'Step 2
|
|
411
|
-
capture_open_questions: 'Step 3
|
|
412
|
-
capture_risks: 'Step 4
|
|
413
|
-
update_work_units_index: 'Step 5
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
461
|
+
update_active_wu_notes: 'Step 1 — WU notes updated',
|
|
462
|
+
capture_decisions: 'Step 2 — decisions captured',
|
|
463
|
+
capture_open_questions: 'Step 3 — open questions captured',
|
|
464
|
+
capture_risks: 'Step 4 — risks captured',
|
|
465
|
+
update_work_units_index: 'Step 5 — work-units index updated',
|
|
466
|
+
recompile_state_md: 'Step 5b — STATE.md generated regions recompiled (D132)',
|
|
467
|
+
update_state_md: 'Step 6 — STATE.md updated',
|
|
468
|
+
write_session_log: 'Step 7 — session log written',
|
|
469
|
+
confirm_no_loss: 'Step 8 — confirm-no-loss gate',
|
|
470
|
+
report: 'Step 9 — report',
|
|
418
471
|
};
|
|
419
472
|
for (const [stepId, state] of Object.entries(payload.steps)) {
|
|
420
473
|
const label = STEP_LABELS[stepId] ?? stepId;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nuos-catalogue state compile` — STATE.md hybrid-document recompile (WU 113b / D132).
|
|
3
|
+
*
|
|
4
|
+
* Reads canonical state from the **live markdown registers** (not the workflow
|
|
5
|
+
* store, which is stale under Mode 1) and splices the generated sections into
|
|
6
|
+
* the sentinel-delimited regions of STATE.md, leaving all authored prose
|
|
7
|
+
* byte-for-byte identical.
|
|
8
|
+
*
|
|
9
|
+
* **Source-of-truth for each generated region (D129 / Mode 1):**
|
|
10
|
+
* - Active WU: `.nuos-catalogue/active-wu` marker file (WU 136 pointer)
|
|
11
|
+
* + title/status resolved from `work-units/_index.md`
|
|
12
|
+
* - WUs in progress: 🟡 row count in `work-units/_index.md`
|
|
13
|
+
* - WUs completed: file count in `work-units/done/`
|
|
14
|
+
* - Blocked WUs: 🔴 rows in `work-units/_index.md`
|
|
15
|
+
* - Decisions: `decisions/_index.md` active section
|
|
16
|
+
* - Open questions: `open-questions/_index.md` active section
|
|
17
|
+
* - Risks: `risks/_index.md` active section
|
|
18
|
+
*
|
|
19
|
+
* The workflow store (`workflows.json`) is accepted as a parameter for API
|
|
20
|
+
* compatibility (the CLI always opens it), but is NOT consulted for any of
|
|
21
|
+
* the above — it is frozen at migration time and would produce stale counts.
|
|
22
|
+
*
|
|
23
|
+
* **No LLM in this path.** The adapter builds an `LLMCompilationOutput`
|
|
24
|
+
* directly from disk state. `renderArticleMarkdown` is called per section,
|
|
25
|
+
* then `spliceGeneratedRegions` writes only inside the sentinel pairs.
|
|
26
|
+
*
|
|
27
|
+
* **First-cutover boundary.** If a sentinel region is absent from the target
|
|
28
|
+
* STATE.md, this command reports the missing regions clearly and exits
|
|
29
|
+
* non-zero without guessing where to insert them. The one-time insertion of
|
|
30
|
+
* sentinels into the live file is a manual operator step (Stage B walkthrough).
|
|
31
|
+
*
|
|
32
|
+
* D132 / D129 boundary:
|
|
33
|
+
* - Generated regions: live markdown registers are source of truth; disk is
|
|
34
|
+
* rendered projection for these regions only.
|
|
35
|
+
* - Authored regions: disk remains the edit base (untouched by this command).
|
|
36
|
+
*/
|
|
37
|
+
import type { LLMCompilationOutput, SentinelConfig } from '@nusoft/nuwiki';
|
|
38
|
+
import { checkArticleDrift } from '@nusoft/nuwiki';
|
|
39
|
+
import type { WorkflowStore } from '../migrate/store.js';
|
|
40
|
+
export declare const STATE_SENTINEL_CONFIG: SentinelConfig;
|
|
41
|
+
export declare const STATE_REGION_KEYS: {
|
|
42
|
+
readonly METADATA: "metadata";
|
|
43
|
+
readonly WHAT_IS_NEXT: "what_is_next";
|
|
44
|
+
readonly OPEN_QUESTIONS: "open_questions";
|
|
45
|
+
readonly RECENT_DECISIONS: "recent_decisions";
|
|
46
|
+
readonly RISKS: "risks";
|
|
47
|
+
readonly HEALTH_CHECK: "health_check";
|
|
48
|
+
};
|
|
49
|
+
export type StateRegionKey = (typeof STATE_REGION_KEYS)[keyof typeof STATE_REGION_KEYS];
|
|
50
|
+
export interface StateSourceAdapterInput {
|
|
51
|
+
store: WorkflowStore;
|
|
52
|
+
buildRoot: string;
|
|
53
|
+
now?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface StateCompiledOutput {
|
|
56
|
+
/** The structured body — one section per generated region. */
|
|
57
|
+
compilationOutput: LLMCompilationOutput;
|
|
58
|
+
/** The generated region contents keyed by region key (ready for splice). */
|
|
59
|
+
regions: Record<StateRegionKey, string>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Reads canonical state from the live markdown registers and the active-WU
|
|
63
|
+
* marker file, and produces the generated content for each STATE.md region.
|
|
64
|
+
*
|
|
65
|
+
* No LLM call is made. The adapter derives all content deterministically.
|
|
66
|
+
* The workflow store parameter is accepted for API compatibility but is not
|
|
67
|
+
* consulted — see module-level comment for the source-of-truth map.
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildStateCompilationOutput(input: StateSourceAdapterInput): Promise<StateCompiledOutput>;
|
|
70
|
+
export interface StateCompileResult {
|
|
71
|
+
output: string;
|
|
72
|
+
exitCode: number;
|
|
73
|
+
updatedRegions?: string[];
|
|
74
|
+
unchangedRegions?: string[];
|
|
75
|
+
}
|
|
76
|
+
export declare function cmdStateCompile(store: WorkflowStore, args: {
|
|
77
|
+
buildRoot: string;
|
|
78
|
+
stateMdPath?: string;
|
|
79
|
+
dryRun?: boolean;
|
|
80
|
+
now?: string;
|
|
81
|
+
}): Promise<StateCompileResult>;
|
|
82
|
+
/**
|
|
83
|
+
* Expose `checkArticleDrift` with STATE.md's sentinel config pre-applied.
|
|
84
|
+
* Used by the pre-commit hook (Stage B) and tests.
|
|
85
|
+
*/
|
|
86
|
+
export declare function checkStateMdDrift(fileContent: string, expectedRegions: Record<string, string>): ReturnType<typeof checkArticleDrift>;
|
|
87
|
+
export interface StateDriftCheckResult {
|
|
88
|
+
output: string;
|
|
89
|
+
exitCode: number;
|
|
90
|
+
/** 'clean' | 'drifted' | 'skipped' — used by tests */
|
|
91
|
+
verdict: 'clean' | 'drifted' | 'skipped';
|
|
92
|
+
driftedRegions?: string[];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check whether the generated regions of STATE.md match what the canonical
|
|
96
|
+
* state currently produces. Designed to be called by the pre-commit hook.
|
|
97
|
+
*
|
|
98
|
+
* Exit-code contract (fail-open):
|
|
99
|
+
* - exit 0 when generated regions are clean
|
|
100
|
+
* - exit 0 when STATE.md has no sentinel regions yet (pre-cutover)
|
|
101
|
+
* - exit 0 when the check cannot run (STATE.md unreadable, store missing)
|
|
102
|
+
* - exit 1 ONLY on confirmed generated-region drift
|
|
103
|
+
*/
|
|
104
|
+
export declare function cmdStateDriftCheck(store: WorkflowStore, args: {
|
|
105
|
+
buildRoot: string;
|
|
106
|
+
stateMdPath?: string;
|
|
107
|
+
now?: string;
|
|
108
|
+
}): Promise<StateDriftCheckResult>;
|