@cleocode/contracts 2026.10.6 → 2026.10.7-rc.ci.674
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/dist/__tests__/evidence-atom-schema.test.js +4 -2
- package/dist/__tests__/evidence-atom-schema.test.js.map +1 -1
- package/dist/acceptance-gate.d.ts +2 -1
- package/dist/acceptance-gate.d.ts.map +1 -1
- package/dist/ask-enforce-hook.d.ts +59 -0
- package/dist/ask-enforce-hook.d.ts.map +1 -0
- package/dist/ask-enforce-hook.js +18 -0
- package/dist/ask-enforce-hook.js.map +1 -0
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +114 -0
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/done-plan.d.ts +1 -1
- package/dist/done-plan.d.ts.map +1 -1
- package/dist/evidence-atom-schema.d.ts +20 -1
- package/dist/evidence-atom-schema.d.ts.map +1 -1
- package/dist/evidence-atom-schema.js +23 -1
- package/dist/evidence-atom-schema.js.map +1 -1
- package/dist/git-hooks.d.ts +50 -0
- package/dist/git-hooks.d.ts.map +1 -0
- package/dist/git-hooks.js +20 -0
- package/dist/git-hooks.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/nexus-account.d.ts +1 -1
- package/dist/nexus-account.d.ts.map +1 -1
- package/dist/nexus-account.js +10 -0
- package/dist/nexus-account.js.map +1 -1
- package/dist/nexus-cloud.d.ts +15 -0
- package/dist/nexus-cloud.d.ts.map +1 -1
- package/dist/nexus-cloud.js.map +1 -1
- package/dist/nexus-vault.d.ts +68 -1
- package/dist/nexus-vault.d.ts.map +1 -1
- package/dist/operations/docs.d.ts +75 -1
- package/dist/operations/docs.d.ts.map +1 -1
- package/dist/operations/orchestrate.d.ts +15 -1
- package/dist/operations/orchestrate.d.ts.map +1 -1
- package/dist/operations/validate.d.ts +5 -0
- package/dist/operations/validate.d.ts.map +1 -1
- package/dist/release/evidence-atoms.d.ts +2 -0
- package/dist/release/evidence-atoms.d.ts.map +1 -1
- package/dist/release/evidence-atoms.js +4 -0
- package/dist/release/evidence-atoms.js.map +1 -1
- package/dist/release/plan.d.ts +3 -3
- package/dist/task.d.ts +36 -0
- package/dist/task.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/evidence-atom-schema.test.ts +4 -2
- package/src/acceptance-gate.ts +2 -1
- package/src/ask-enforce-hook.ts +69 -0
- package/src/dispatch/operations-registry.ts +118 -0
- package/src/done-plan.ts +1 -0
- package/src/evidence-atom-schema.ts +24 -1
- package/src/git-hooks.ts +43 -0
- package/src/index.ts +11 -0
- package/src/nexus-account.ts +10 -0
- package/src/nexus-cloud.ts +16 -0
- package/src/nexus-vault.ts +71 -1
- package/src/operations/docs.ts +88 -2
- package/src/operations/orchestrate.ts +15 -1
- package/src/operations/validate.ts +5 -0
- package/src/release/evidence-atoms.ts +4 -0
- package/src/task.ts +37 -0
|
@@ -30,12 +30,14 @@ import type { VerificationGate } from '../task.js';
|
|
|
30
30
|
describe('EvidenceAtomSchema (T10337)', () => {
|
|
31
31
|
it('EVIDENCE_ATOM_KINDS covers every discriminated union member (T12030)', () => {
|
|
32
32
|
// Verify the shared constant is in sync with the zod discriminated union.
|
|
33
|
-
// The discriminated union keys are 'commit','files','test-run','
|
|
34
|
-
// 'note','decision','pr','ci','loc-drop','callsite-coverage',
|
|
33
|
+
// The discriminated union keys are 'commit','files','test-run','qa-run',
|
|
34
|
+
// 'tool','url','note','decision','pr','ci','loc-drop','callsite-coverage',
|
|
35
|
+
// 'satisfies' ('qa-run': T13427).
|
|
35
36
|
const expected = [
|
|
36
37
|
'commit',
|
|
37
38
|
'files',
|
|
38
39
|
'test-run',
|
|
40
|
+
'qa-run',
|
|
39
41
|
'tool',
|
|
40
42
|
'url',
|
|
41
43
|
'note',
|
package/src/acceptance-gate.ts
CHANGED
|
@@ -124,7 +124,8 @@ export interface TestGate extends GateBase {
|
|
|
124
124
|
/** Arguments split explicitly (avoids shell-injection). */
|
|
125
125
|
args?: string[];
|
|
126
126
|
/**
|
|
127
|
-
* - `"pass"`: exit code 0 AND stdout contains no `FAIL|failing|Error:` pattern
|
|
127
|
+
* - `"pass"`: exit code 0 AND stdout contains no `FAIL|failing|Error:` pattern, ignoring
|
|
128
|
+
* summaries that report zero failures (`ℹ fail 0`, `# fail 0`, `0 failed`; T13511).
|
|
128
129
|
* - `"exit0"`: exit code 0 only (permissive mode).
|
|
129
130
|
*/
|
|
130
131
|
expect: 'pass' | 'exit0';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask-enforce Stop hook — shared types (T13420).
|
|
3
|
+
*
|
|
4
|
+
* The owner rule (CLEO-INJECTION.md universal protocol step 7) routes every
|
|
5
|
+
* owner question or decision through the harness ask tool. The ask-enforce
|
|
6
|
+
* hook runs when an agent's turn ends: if the final reply asks the owner
|
|
7
|
+
* something in prose and the turn made no ask-tool call, the hook blocks the
|
|
8
|
+
* stop and tells the agent to re-ask with its ask tool. The classifier lives
|
|
9
|
+
* in `@cleocode/core` (`harness/ask-enforce`); the provider wire formats and
|
|
10
|
+
* the entry point (`cleo hook ask-enforce`) in `@cleocode/cleo`.
|
|
11
|
+
*
|
|
12
|
+
* Design: `cleo docs fetch t13420-ask-enforce-stop-hook-design`.
|
|
13
|
+
*
|
|
14
|
+
* @task T13420
|
|
15
|
+
* @epic T13418
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How the hook treats a turn that ends in a prose owner question.
|
|
20
|
+
*
|
|
21
|
+
* - `block` — DEFAULT. Block the stop once per turn with the re-ask message.
|
|
22
|
+
* - `warn` — never block; surface the message as context only.
|
|
23
|
+
* - `off` — do nothing.
|
|
24
|
+
*/
|
|
25
|
+
export type AskEnforceMode = 'block' | 'warn' | 'off';
|
|
26
|
+
|
|
27
|
+
/** What the classifier sees for one ended turn. */
|
|
28
|
+
export interface AskEnforceInput {
|
|
29
|
+
/** The final assistant message of the turn (empty or absent: fail open). */
|
|
30
|
+
readonly lastAssistantText: string | null | undefined;
|
|
31
|
+
/** Tool names called since the last user message. */
|
|
32
|
+
readonly turnToolCalls: readonly string[];
|
|
33
|
+
/** Every tool name that counts as an ask-tool call (provider names, CAAMP union). */
|
|
34
|
+
readonly askToolNames: readonly string[];
|
|
35
|
+
/** The harness already re-prompted once from a stop hook in this turn. */
|
|
36
|
+
readonly stopHookActive?: boolean;
|
|
37
|
+
/** Blocks this hook already issued in this turn (loop guard for harnesses without `stopHookActive`). */
|
|
38
|
+
readonly blocksThisTurn?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Which rule decided the verdict.
|
|
43
|
+
*
|
|
44
|
+
* - `no-text` — nothing to classify (fail open).
|
|
45
|
+
* - `asked` — the turn already called an ask tool.
|
|
46
|
+
* - `loop-guard` — the stop was already blocked once this turn.
|
|
47
|
+
* - `hitl-request` — the reply carries a `hitl.request` envelope (no-ask-tool fallback).
|
|
48
|
+
* - `clean` — no owner question in the reply's tail.
|
|
49
|
+
* - `prose-question` — a reader-directed question ends the reply.
|
|
50
|
+
* - `written-decision` — a request for an owner choice or approval without `?`.
|
|
51
|
+
*/
|
|
52
|
+
export type AskEnforceSignal =
|
|
53
|
+
| 'no-text'
|
|
54
|
+
| 'asked'
|
|
55
|
+
| 'loop-guard'
|
|
56
|
+
| 'hitl-request'
|
|
57
|
+
| 'clean'
|
|
58
|
+
| 'prose-question'
|
|
59
|
+
| 'written-decision';
|
|
60
|
+
|
|
61
|
+
/** The classifier's answer for one ended turn. */
|
|
62
|
+
export interface AskEnforceVerdict {
|
|
63
|
+
/** `block` only for `prose-question` / `written-decision`. */
|
|
64
|
+
readonly verdict: 'allow' | 'block';
|
|
65
|
+
/** The rule that decided. */
|
|
66
|
+
readonly signal: AskEnforceSignal;
|
|
67
|
+
/** The offending sentence, at most 160 characters (block only). */
|
|
68
|
+
readonly excerpt: string | null;
|
|
69
|
+
}
|
|
@@ -100,6 +100,66 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
100
100
|
],
|
|
101
101
|
},
|
|
102
102
|
},
|
|
103
|
+
{
|
|
104
|
+
gateway: 'mutate',
|
|
105
|
+
domain: 'tasks',
|
|
106
|
+
operation: 'req.replace',
|
|
107
|
+
description:
|
|
108
|
+
"Replace a typed requirement gate's definition in place, keeping its AC row and REQ-ID; the superseded gate is kept in AC history (T12988)",
|
|
109
|
+
tier: 1,
|
|
110
|
+
idempotent: true,
|
|
111
|
+
sessionRequired: true,
|
|
112
|
+
requiredParams: ['taskId', 'req', 'gate'],
|
|
113
|
+
params: [
|
|
114
|
+
requirementTaskParam,
|
|
115
|
+
{
|
|
116
|
+
name: 'req',
|
|
117
|
+
type: 'string',
|
|
118
|
+
required: true,
|
|
119
|
+
description: 'REQ-ID of the gate to replace',
|
|
120
|
+
cli: { positional: true },
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'gate',
|
|
124
|
+
type: 'string',
|
|
125
|
+
required: true,
|
|
126
|
+
description: 'Replacement AcceptanceGate JSON; its req must be absent or equal the REQ-ID',
|
|
127
|
+
cli: { flag: 'gate' },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'reason',
|
|
131
|
+
type: 'string',
|
|
132
|
+
required: false,
|
|
133
|
+
description: 'Why the gate changes; required once the task is in a locked pipeline stage',
|
|
134
|
+
cli: { flag: 'reason' },
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
inputSchema: {
|
|
138
|
+
operation: 'tasks.req.replace',
|
|
139
|
+
schema: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
required: ['taskId', 'req', 'gate'],
|
|
142
|
+
additionalProperties: false,
|
|
143
|
+
properties: {
|
|
144
|
+
taskId: { type: 'string', minLength: 1 },
|
|
145
|
+
req: { type: 'string', minLength: 1 },
|
|
146
|
+
gate: { type: 'string', minLength: 1 },
|
|
147
|
+
reason: { type: 'string', minLength: 1 },
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
examples: [
|
|
151
|
+
{
|
|
152
|
+
name: 'repo-relative test gate',
|
|
153
|
+
value: {
|
|
154
|
+
taskId: 'T121',
|
|
155
|
+
req: 'PARTNER-121',
|
|
156
|
+
gate: '{"kind":"test","command":"pnpm","args":["--filter","app","exec","vitest","run","src/a.test.ts"],"expect":"pass","description":"Task tests pass","req":"PARTNER-121"}',
|
|
157
|
+
reason: 'gate pointed at a removed worktree',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
103
163
|
{
|
|
104
164
|
gateway: 'query',
|
|
105
165
|
domain: 'tasks',
|
|
@@ -1849,6 +1909,14 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
1849
1909
|
required: false,
|
|
1850
1910
|
description: 'Actor attribution recorded on each gate result',
|
|
1851
1911
|
},
|
|
1912
|
+
{
|
|
1913
|
+
name: 'req',
|
|
1914
|
+
type: 'string',
|
|
1915
|
+
required: false,
|
|
1916
|
+
description:
|
|
1917
|
+
'Comma-separated REQ-IDs: run and cache only these typed gates; the others are not run (T13486)',
|
|
1918
|
+
cli: { flag: 'req' },
|
|
1919
|
+
},
|
|
1852
1920
|
] satisfies ParamDef[],
|
|
1853
1921
|
},
|
|
1854
1922
|
// check.archive.stats incoming from admin (T5615)
|
|
@@ -8510,6 +8578,56 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
8510
8578
|
},
|
|
8511
8579
|
],
|
|
8512
8580
|
},
|
|
8581
|
+
// ── docs.doctor (T13447 — Epic T13340 / Saga T13339) ──────────────────────
|
|
8582
|
+
{
|
|
8583
|
+
gateway: 'query',
|
|
8584
|
+
domain: 'docs',
|
|
8585
|
+
operation: 'doctor',
|
|
8586
|
+
description:
|
|
8587
|
+
'docs.doctor (query) — docs store health diagnostics (dry-run): dangling local-file ' +
|
|
8588
|
+
'pointers, docVersion skew vs the audit log, empty topics/related_tasks provenance, ' +
|
|
8589
|
+
'docs_wikilinks drift, legacy store surfaces, stale drafts. Reports a repair plan.',
|
|
8590
|
+
tier: 1,
|
|
8591
|
+
idempotent: true,
|
|
8592
|
+
sessionRequired: false,
|
|
8593
|
+
requiredParams: [],
|
|
8594
|
+
params: [
|
|
8595
|
+
{
|
|
8596
|
+
name: 'olderThanDays',
|
|
8597
|
+
type: 'number' as const,
|
|
8598
|
+
required: false,
|
|
8599
|
+
description: 'Age threshold in days for the stale-drafts check (default 30)',
|
|
8600
|
+
},
|
|
8601
|
+
],
|
|
8602
|
+
},
|
|
8603
|
+
{
|
|
8604
|
+
gateway: 'mutate',
|
|
8605
|
+
domain: 'docs',
|
|
8606
|
+
operation: 'doctor',
|
|
8607
|
+
description:
|
|
8608
|
+
'docs.doctor (mutate) — apply the docs doctor repairs: set docVersion from the audit ' +
|
|
8609
|
+
'log, backfill topics/related_tasks from content, archive dangling local-file rows, ' +
|
|
8610
|
+
'rebuild docs_wikilinks. The dispatch layer creates a fresh backup (cleo backup add ' +
|
|
8611
|
+
'path) and requires its receipt before any write.',
|
|
8612
|
+
tier: 1,
|
|
8613
|
+
idempotent: false,
|
|
8614
|
+
sessionRequired: false,
|
|
8615
|
+
requiredParams: [],
|
|
8616
|
+
params: [
|
|
8617
|
+
{
|
|
8618
|
+
name: 'apply',
|
|
8619
|
+
type: 'boolean' as const,
|
|
8620
|
+
required: false,
|
|
8621
|
+
description: 'Execute repairs (default false — dry-run plan only)',
|
|
8622
|
+
},
|
|
8623
|
+
{
|
|
8624
|
+
name: 'olderThanDays',
|
|
8625
|
+
type: 'number' as const,
|
|
8626
|
+
required: false,
|
|
8627
|
+
description: 'Age threshold in days for the stale-drafts check (default 30)',
|
|
8628
|
+
},
|
|
8629
|
+
],
|
|
8630
|
+
},
|
|
8513
8631
|
// ── docs.generate (T798) ─────────────────────────────────────────────────
|
|
8514
8632
|
{
|
|
8515
8633
|
gateway: 'query',
|
package/src/done-plan.ts
CHANGED
|
@@ -57,6 +57,7 @@ export const EVIDENCE_ATOM_KINDS = [
|
|
|
57
57
|
'commit',
|
|
58
58
|
'files',
|
|
59
59
|
'test-run',
|
|
60
|
+
'qa-run',
|
|
60
61
|
'tool',
|
|
61
62
|
'url',
|
|
62
63
|
'note',
|
|
@@ -126,6 +127,22 @@ export const testRunAtomSchema = z.object({
|
|
|
126
127
|
path: z.string().min(1, 'test-run atom requires a non-empty path'),
|
|
127
128
|
});
|
|
128
129
|
|
|
130
|
+
/**
|
|
131
|
+
* `qa-run:<path>` atom — path to a JSON receipt of a native typecheck or lint
|
|
132
|
+
* run (T13427): `{kind, command, exitCode, diagnostics: {errors}, roots,
|
|
133
|
+
* startTime?, tool?: {name, version}}`.
|
|
134
|
+
*
|
|
135
|
+
* Format: `qa-run:<absolute or project-relative path>`. The runtime validator
|
|
136
|
+
* requires exit 0 and zero errors, binds the receipt to the change (fresh,
|
|
137
|
+
* covering every changed code path) and pins the tree hash.
|
|
138
|
+
*
|
|
139
|
+
* @task T13427
|
|
140
|
+
*/
|
|
141
|
+
export const qaRunAtomSchema = z.object({
|
|
142
|
+
kind: z.literal('qa-run'),
|
|
143
|
+
path: z.string().min(1, 'qa-run atom requires a non-empty path'),
|
|
144
|
+
});
|
|
145
|
+
|
|
129
146
|
/**
|
|
130
147
|
* `tool:<name>` atom — runs a project-resolved canonical tool and accepts
|
|
131
148
|
* exit-code 0.
|
|
@@ -391,6 +408,7 @@ export const satisfiesAtomSchema = z.object({
|
|
|
391
408
|
* - `commit` — git commit SHA (T832)
|
|
392
409
|
* - `files` — touched file paths (T832)
|
|
393
410
|
* - `test-run` — structured test runner JSON (T832)
|
|
411
|
+
* - `qa-run` — native typecheck/lint receipt JSON (T13427)
|
|
394
412
|
* - `tool` — project-resolved tool exit code (T832 / T1534)
|
|
395
413
|
* - `url` — soft external pointer (T832)
|
|
396
414
|
* - `note` — free-form note (T832)
|
|
@@ -408,6 +426,7 @@ export const EvidenceAtomSchema = z.discriminatedUnion('kind', [
|
|
|
408
426
|
commitAtomSchema,
|
|
409
427
|
filesAtomSchema,
|
|
410
428
|
testRunAtomSchema,
|
|
429
|
+
qaRunAtomSchema,
|
|
411
430
|
toolAtomSchema,
|
|
412
431
|
urlAtomSchema,
|
|
413
432
|
noteAtomSchema,
|
|
@@ -506,7 +525,7 @@ export const GATE_EVIDENCE_REQUIREMENTS: Readonly<
|
|
|
506
525
|
],
|
|
507
526
|
},
|
|
508
527
|
testsPassed: { oneOf: [['test-run'], ['tool'], ['ci']] },
|
|
509
|
-
qaPassed: { oneOf: [['tool'], ['ci']] },
|
|
528
|
+
qaPassed: { oneOf: [['tool'], ['ci'], ['qa-run']] },
|
|
510
529
|
documented: { oneOf: [['files'], ['url']] },
|
|
511
530
|
securityPassed: { oneOf: [['tool'], ['note']] },
|
|
512
531
|
cleanupDone: { oneOf: [['note']] },
|
|
@@ -565,6 +584,7 @@ const ATOM_EXAMPLES: Readonly<Record<EvidenceAtomKind, string>> = Object.freeze(
|
|
|
565
584
|
commit: 'commit:<sha>',
|
|
566
585
|
files: 'files:path/a.ts,path/b.ts',
|
|
567
586
|
'test-run': 'test-run:/tmp/vitest-out.json',
|
|
587
|
+
'qa-run': 'qa-run:/tmp/typecheck-receipt.json',
|
|
568
588
|
tool: 'tool:test',
|
|
569
589
|
url: 'url:https://example.com/docs',
|
|
570
590
|
note: 'note:<short description>',
|
|
@@ -847,6 +867,9 @@ export function parseEvidenceString(raw: string): EvidenceAtom[] {
|
|
|
847
867
|
case 'test-run':
|
|
848
868
|
atoms.push({ kind: 'test-run', path: payload });
|
|
849
869
|
break;
|
|
870
|
+
case 'qa-run':
|
|
871
|
+
atoms.push({ kind: 'qa-run', path: payload });
|
|
872
|
+
break;
|
|
850
873
|
case 'tool':
|
|
851
874
|
atoms.push({ kind: 'tool', tool: payload });
|
|
852
875
|
break;
|
package/src/git-hooks.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Hash-guarded Git hook installation and recovery contracts. */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/** Canonical names of the three shipped Git checks. */
|
|
5
|
+
export const CLEO_GIT_HOOK_NAMES = ['commit-msg', 'pre-commit', 'pre-push'] as const;
|
|
6
|
+
|
|
7
|
+
/** One recoverable managed Git hook change. */
|
|
8
|
+
export const GitHookChangeSchema = z.object({
|
|
9
|
+
name: z.enum(CLEO_GIT_HOOK_NAMES),
|
|
10
|
+
before: z.string().nullable(),
|
|
11
|
+
beforeMode: z.number().int().min(0).max(0o777).nullable(),
|
|
12
|
+
afterHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
13
|
+
});
|
|
14
|
+
/** A receipt scoped to the effective Git hook directory. */
|
|
15
|
+
export const GitHookInstallReceiptSchema = z.object({
|
|
16
|
+
schemaVersion: z.literal(1),
|
|
17
|
+
hooksDir: z.string(),
|
|
18
|
+
changes: z.array(GitHookChangeSchema),
|
|
19
|
+
});
|
|
20
|
+
/** One managed change with its recovery image. */
|
|
21
|
+
export type GitHookChange = z.infer<typeof GitHookChangeSchema>;
|
|
22
|
+
/** Installation image used only after matching the current content hash. */
|
|
23
|
+
export type GitHookInstallReceipt = z.infer<typeof GitHookInstallReceiptSchema>;
|
|
24
|
+
|
|
25
|
+
/** Exact hashes of historical shipped templates; marker text is insufficient. */
|
|
26
|
+
export const GitHookLegacyHashesSchema = z.record(
|
|
27
|
+
z.string(),
|
|
28
|
+
z.array(z.string().regex(/^[a-f0-9]{64}$/)),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
/** Explicit installer controls shared by SDK wrappers and CLI delivery. */
|
|
32
|
+
export interface GitHookInstallOptions {
|
|
33
|
+
/** Override shipped template location. */
|
|
34
|
+
templatesDir?: string;
|
|
35
|
+
/** Deprecated compatibility flag; never bypasses ownership or tracked-path safety. */
|
|
36
|
+
force?: boolean;
|
|
37
|
+
/** Preview without filesystem mutations. */
|
|
38
|
+
dryRun?: boolean;
|
|
39
|
+
/** Restore matching local installation images. */
|
|
40
|
+
rollbackReceipt?: GitHookInstallReceipt;
|
|
41
|
+
/** Explicitly permit writes into an effective hooksPath inside the working tree. */
|
|
42
|
+
allowTrackedHooksPath?: boolean;
|
|
43
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -85,6 +85,13 @@ export type {
|
|
|
85
85
|
CycleTimeDistribution,
|
|
86
86
|
CycleTimePercentiles,
|
|
87
87
|
} from './archive.js';
|
|
88
|
+
// === Ask-enforce Stop hook (T13420 / Epic T13418) ===
|
|
89
|
+
export type {
|
|
90
|
+
AskEnforceInput,
|
|
91
|
+
AskEnforceMode,
|
|
92
|
+
AskEnforceSignal,
|
|
93
|
+
AskEnforceVerdict,
|
|
94
|
+
} from './ask-enforce-hook.js';
|
|
88
95
|
// === Attachment Types ===
|
|
89
96
|
export type {
|
|
90
97
|
Attachment,
|
|
@@ -613,6 +620,7 @@ export {
|
|
|
613
620
|
noteAtomSchema,
|
|
614
621
|
parseEvidenceString,
|
|
615
622
|
prAtomSchema,
|
|
623
|
+
qaRunAtomSchema,
|
|
616
624
|
SATISFIES_TASK_ID_REGEX,
|
|
617
625
|
SATISFIES_VERSION_PIN_REGEX,
|
|
618
626
|
satisfiesAtomSchema,
|
|
@@ -3158,6 +3166,9 @@ export {
|
|
|
3158
3166
|
type CloudLeaseReleaseResult,
|
|
3159
3167
|
type CloudPushResult,
|
|
3160
3168
|
type CloudRestoreResult,
|
|
3169
|
+
type CloudSyncPushEnableResult,
|
|
3170
|
+
type CloudSyncResult,
|
|
3171
|
+
type CloudSyncStreamResult,
|
|
3161
3172
|
type CloudVaultLease,
|
|
3162
3173
|
type CloudVaultScope,
|
|
3163
3174
|
type CloudVaultSnapshot,
|
package/src/nexus-account.ts
CHANGED
|
@@ -101,6 +101,16 @@ export const NEXUS_ACCOUNT_ERROR_CODES = [
|
|
|
101
101
|
'E_NEXUS_VAULT_STORE_BUSY',
|
|
102
102
|
/** A project restore target already holds a different project. */
|
|
103
103
|
'E_NEXUS_VAULT_TARGET_OCCUPIED',
|
|
104
|
+
// Change-journal push (`cleo sync enable push`, T12343 S4-1b).
|
|
105
|
+
/** The store is not fit to reach genesis (a precondition failed); nothing was cut. */
|
|
106
|
+
'E_NEXUS_SYNC_REFUSED',
|
|
107
|
+
/**
|
|
108
|
+
* Another device already started this stream's change journal: this store restores its journal
|
|
109
|
+
* checkpoint, then joins the stream (T12999), never writing a second genesis.
|
|
110
|
+
*/
|
|
111
|
+
'E_NEXUS_SYNC_STREAM_JOURNALED',
|
|
112
|
+
/** `cleo cloud sync` found no stream with `sync.push` or `sync.pull` on (`cleo sync enable`). */
|
|
113
|
+
'E_SYNC_DISABLED',
|
|
104
114
|
// Projects by name (`cleo cloud restore <name>`, T13102).
|
|
105
115
|
/** No project of the account has that name, label or id. */
|
|
106
116
|
'E_NEXUS_PROJECT_NOT_FOUND',
|
package/src/nexus-cloud.ts
CHANGED
|
@@ -614,6 +614,20 @@ export interface CloudStatusUndoBudget {
|
|
|
614
614
|
readonly exceededAt: string | null;
|
|
615
615
|
}
|
|
616
616
|
|
|
617
|
+
/**
|
|
618
|
+
* The pull's seen-transaction ledger (`_sync_seen_txn`, T13317). The cloud
|
|
619
|
+
* pull prunes each origin's rows below its staged `local_seq` floor (T13318);
|
|
620
|
+
* a pruned transaction delivered again is refused, never applied twice.
|
|
621
|
+
*/
|
|
622
|
+
export interface CloudStatusSeenTxns {
|
|
623
|
+
/** Rows held, every stream. */
|
|
624
|
+
readonly rows: number;
|
|
625
|
+
/** Estimated bytes: the stored text and integer payload, without SQLite page overhead. */
|
|
626
|
+
readonly bytes: number;
|
|
627
|
+
/** Rows per stream. */
|
|
628
|
+
readonly byStream: Readonly<Record<string, number>>;
|
|
629
|
+
}
|
|
630
|
+
|
|
617
631
|
export interface CloudStatusSyncStream {
|
|
618
632
|
/** Which store. */
|
|
619
633
|
readonly scope: 'project' | 'global';
|
|
@@ -639,6 +653,8 @@ export interface CloudStatusSyncStream {
|
|
|
639
653
|
readonly held: CloudStatusHeld;
|
|
640
654
|
/** Undo held against its budget (§3.5 Rule 2, D5). */
|
|
641
655
|
readonly undo: CloudStatusUndoBudget;
|
|
656
|
+
/** The seen-transaction ledger's size; it grows until pruning has a proven floor (T13317). */
|
|
657
|
+
readonly seenTxns: CloudStatusSeenTxns;
|
|
642
658
|
/** Sealed ops not yet handed to the outbox for sending. */
|
|
643
659
|
readonly unsentOps: CloudSyncFact<number>;
|
|
644
660
|
/** The last sequence this store pushed to the server. */
|
package/src/nexus-vault.ts
CHANGED
|
@@ -211,6 +211,76 @@ export interface CloudPushResult {
|
|
|
211
211
|
warnings: CloudWarning[];
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/** `cleo sync enable push`: the stream's genesis cut and its genesis checkpoint (T12343 S4-1b). */
|
|
215
|
+
export interface CloudSyncPushEnableResult {
|
|
216
|
+
apiUrl: string;
|
|
217
|
+
scope: CloudVaultScope;
|
|
218
|
+
streamId: string;
|
|
219
|
+
/**
|
|
220
|
+
* `enabled`: the cut was recorded and its genesis checkpoint stored; `resumed`: a cut recorded
|
|
221
|
+
* by an earlier run whose checkpoint push failed was pushed now; `already`: push was already on;
|
|
222
|
+
* `joined`: this store holds the stream's journal checkpoint another device cut, and now pushes its
|
|
223
|
+
* own writes and pulls the stream's after it, with no genesis of its own (T13312).
|
|
224
|
+
*/
|
|
225
|
+
status: 'enabled' | 'resumed' | 'already' | 'joined';
|
|
226
|
+
/** The genesis cut: the highest capture seq the checkpoint carries. */
|
|
227
|
+
cut: number;
|
|
228
|
+
/** Transactions sealed before the cut, folded into the checkpoint, and rows given genesis row meta. */
|
|
229
|
+
sealed: number;
|
|
230
|
+
folded: number;
|
|
231
|
+
baselined: Record<string, number>;
|
|
232
|
+
/** The genesis checkpoint (null when `already`). */
|
|
233
|
+
snapshot: CloudVaultSnapshot | null;
|
|
234
|
+
/** The delta segment appended so the counts reconcile with the parent, if one was needed. */
|
|
235
|
+
deltaSegmentSeq: number | null;
|
|
236
|
+
/** The replicaSeq this replica's first journal segment follows (null: it starts at 0). */
|
|
237
|
+
replicaSeqFloor: number | null;
|
|
238
|
+
warnings: CloudWarning[];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** One stream of `cleo cloud sync` (T12996). */
|
|
242
|
+
export interface CloudSyncStreamResult {
|
|
243
|
+
scope: CloudVaultScope;
|
|
244
|
+
streamId: string | null;
|
|
245
|
+
/**
|
|
246
|
+
* `synced`: pushed and pulled what was enabled; `disabled`: neither `sync.push` nor `sync.pull` is on;
|
|
247
|
+
* `paused`: push paused for a device clock ahead of the server's; `not-attached`: this machine has no
|
|
248
|
+
* store on that stream; `refused`: a precondition or the server refused (see `refused`); `failed`: an
|
|
249
|
+
* error stopped this stream (see `refused`); the other streams still synced.
|
|
250
|
+
*/
|
|
251
|
+
status: 'synced' | 'disabled' | 'paused' | 'not-attached' | 'refused' | 'failed';
|
|
252
|
+
/** Why this stream did not sync fully, or null. */
|
|
253
|
+
refused: string | null;
|
|
254
|
+
/**
|
|
255
|
+
* Legs with nothing to do, not failures: a flag off, or push before this store has its own genesis
|
|
256
|
+
* (a store that only pulls).
|
|
257
|
+
*/
|
|
258
|
+
skipped: string[];
|
|
259
|
+
/** Transactions sealed, segments persisted, segments the server stored (`duplicates` of them retries). */
|
|
260
|
+
sealed: number;
|
|
261
|
+
built: number;
|
|
262
|
+
sent: number;
|
|
263
|
+
duplicates: number;
|
|
264
|
+
/** Segments received, transactions staged, re-deliveries skipped. */
|
|
265
|
+
received: number;
|
|
266
|
+
staged: number;
|
|
267
|
+
redelivered: number;
|
|
268
|
+
/** Transactions applied; still held (pending, clock skew, or a newer schema); in conflict. */
|
|
269
|
+
applied: number;
|
|
270
|
+
held: number;
|
|
271
|
+
conflicts: number;
|
|
272
|
+
/** The stream position this store has staged up to, and the server's head (null when not pulled). */
|
|
273
|
+
after: number | null;
|
|
274
|
+
head: number | null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** `cleo cloud sync` (T12996): seal, push, pull and apply each attached stream. */
|
|
278
|
+
export interface CloudSyncResult {
|
|
279
|
+
apiUrl: string;
|
|
280
|
+
streams: CloudSyncStreamResult[];
|
|
281
|
+
warnings: CloudWarning[];
|
|
282
|
+
}
|
|
283
|
+
|
|
214
284
|
/** `cleo cloud pull` / `cleo cloud restore`. */
|
|
215
285
|
export interface CloudRestoreResult {
|
|
216
286
|
apiUrl: string;
|
|
@@ -372,7 +442,7 @@ export interface CloudConflict {
|
|
|
372
442
|
columns: string[];
|
|
373
443
|
/** The typed rule, guard message or invariant, when there is one. */
|
|
374
444
|
rule: string | null;
|
|
375
|
-
/** What happened to the incoming op: `incoming-applied`, `incoming-dropped`, `row-deleted`, `op-voided`. */
|
|
445
|
+
/** What happened to the incoming op: `incoming-applied`, `incoming-dropped`, `row-deleted`, `op-voided`, `op-held`. */
|
|
376
446
|
resolution: string;
|
|
377
447
|
opHlc: string;
|
|
378
448
|
localHlc: string | null;
|
package/src/operations/docs.ts
CHANGED
|
@@ -899,6 +899,90 @@ export interface DocsSupersedeResult {
|
|
|
899
899
|
reason?: string;
|
|
900
900
|
}
|
|
901
901
|
|
|
902
|
+
// --------------------------------------------------------------------------
|
|
903
|
+
// docs.doctor — store health diagnostics + safe repairs (T13447)
|
|
904
|
+
// --------------------------------------------------------------------------
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Parameters for `docs.doctor`.
|
|
908
|
+
*
|
|
909
|
+
* Dry-run by default; `apply: true` executes the repairs (the dispatch layer
|
|
910
|
+
* creates a fresh backup first and hands the receipt to core — callers never
|
|
911
|
+
* pass one).
|
|
912
|
+
*
|
|
913
|
+
* @task T13447 (Epic T13340 / Saga T13339)
|
|
914
|
+
*/
|
|
915
|
+
export interface DocsDoctorParams {
|
|
916
|
+
/** Execute repairs instead of producing a dry-run plan. Default false. */
|
|
917
|
+
apply?: boolean;
|
|
918
|
+
/** Age threshold (days) for the `stale-drafts` diagnostic. Default 30. */
|
|
919
|
+
olderThanDays?: number;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* One `docs.doctor` diagnostic class in the report.
|
|
924
|
+
*
|
|
925
|
+
* @task T13447
|
|
926
|
+
*/
|
|
927
|
+
export interface DocsDoctorDiagnostic {
|
|
928
|
+
/** Which check produced this entry. */
|
|
929
|
+
kind:
|
|
930
|
+
| 'dangling-pointers'
|
|
931
|
+
| 'doc-version-skew'
|
|
932
|
+
| 'empty-provenance'
|
|
933
|
+
| 'wikilinks-drift'
|
|
934
|
+
| 'legacy-store-present'
|
|
935
|
+
| 'stale-drafts';
|
|
936
|
+
/** `error` should be fixed promptly; `info` is observational. */
|
|
937
|
+
severity: 'info' | 'warn' | 'error';
|
|
938
|
+
/** Total offenders found (may exceed `items.length`). */
|
|
939
|
+
count: number;
|
|
940
|
+
/** Capped per-item detail. */
|
|
941
|
+
items: ReadonlyArray<Record<string, unknown>>;
|
|
942
|
+
/** True when `count` exceeded the detail cap. */
|
|
943
|
+
truncated: boolean;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* One planned or executed `docs.doctor` repair step.
|
|
948
|
+
*
|
|
949
|
+
* @task T13447
|
|
950
|
+
*/
|
|
951
|
+
export interface DocsDoctorRepairAction {
|
|
952
|
+
/** Diagnostic class this repair addresses. */
|
|
953
|
+
kind: DocsDoctorDiagnostic['kind'];
|
|
954
|
+
/** Machine-readable action verb (e.g. `set-doc-version`). */
|
|
955
|
+
action: string;
|
|
956
|
+
/** False on a dry run (the action is a plan, not a write). */
|
|
957
|
+
applied: boolean;
|
|
958
|
+
/** Doc slug the action touched, when row-scoped. */
|
|
959
|
+
slug?: string;
|
|
960
|
+
/** Attachment id the action touched, when row-scoped. */
|
|
961
|
+
attachmentId?: string;
|
|
962
|
+
/** Human-readable detail (old→new values, derived counts, …). */
|
|
963
|
+
detail: string;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Result of `docs.doctor`.
|
|
968
|
+
*
|
|
969
|
+
* @task T13447
|
|
970
|
+
*/
|
|
971
|
+
export interface DocsDoctorResult {
|
|
972
|
+
/** True when repairs were written (`apply: true`). */
|
|
973
|
+
applied: boolean;
|
|
974
|
+
/** Age threshold used for the `stale-drafts` check. */
|
|
975
|
+
olderThanDays: number;
|
|
976
|
+
/** Backup receipt that authorised this run (null on dry runs). */
|
|
977
|
+
backupReceiptPath: string | null;
|
|
978
|
+
/** All six diagnostics, in declaration order. */
|
|
979
|
+
diagnostics: readonly DocsDoctorDiagnostic[];
|
|
980
|
+
/** Repair plan (dry run) or executed repairs (apply). */
|
|
981
|
+
repairs: readonly DocsDoctorRepairAction[];
|
|
982
|
+
/** One-line human summary. */
|
|
983
|
+
summary: string;
|
|
984
|
+
}
|
|
985
|
+
|
|
902
986
|
// ============================================================================
|
|
903
987
|
// Discriminated Union (DocsOps)
|
|
904
988
|
// ============================================================================
|
|
@@ -921,7 +1005,8 @@ export type DocsOps =
|
|
|
921
1005
|
| { op: 'docs.add'; params: DocsAddParams; result: DocsAddResult }
|
|
922
1006
|
| { op: 'docs.remove'; params: DocsRemoveParams; result: DocsRemoveResult }
|
|
923
1007
|
| { op: 'docs.update'; params: DocsUpdateParams; result: DocsUpdateResult }
|
|
924
|
-
| { op: 'docs.supersede'; params: DocsSupersedeParams; result: DocsSupersedeResult }
|
|
1008
|
+
| { op: 'docs.supersede'; params: DocsSupersedeParams; result: DocsSupersedeResult }
|
|
1009
|
+
| { op: 'docs.doctor'; params: DocsDoctorParams; result: DocsDoctorResult };
|
|
925
1010
|
|
|
926
1011
|
/**
|
|
927
1012
|
* Enumeration of all docs domain operation names.
|
|
@@ -938,4 +1023,5 @@ export type DocsOp =
|
|
|
938
1023
|
| 'docs.add'
|
|
939
1024
|
| 'docs.remove'
|
|
940
1025
|
| 'docs.update'
|
|
941
|
-
| 'docs.supersede'
|
|
1026
|
+
| 'docs.supersede'
|
|
1027
|
+
| 'docs.doctor';
|
|
@@ -561,8 +561,22 @@ export interface OrchestrateSpawnResult {
|
|
|
561
561
|
* @task T963
|
|
562
562
|
*/
|
|
563
563
|
prompt: string;
|
|
564
|
-
/**
|
|
564
|
+
/**
|
|
565
|
+
* The spawned agent's identity: the same id as `worktreeEnv.CLEO_AGENT_ID`,
|
|
566
|
+
* its per-agent session's handle (`agent-<task>`) and the worktree lock
|
|
567
|
+
* holder. Before T13494 this field carried the classifier's persona.
|
|
568
|
+
* @task T963
|
|
569
|
+
* @task T13494
|
|
570
|
+
*/
|
|
565
571
|
agentId: string;
|
|
572
|
+
/**
|
|
573
|
+
* The agent profile the classifier routed the task to (e.g.
|
|
574
|
+
* `project-security-worker`, `cleo-subagent`); mirrors
|
|
575
|
+
* `meta.classify.agentId`. It names what the prompt is written for, not who
|
|
576
|
+
* runs it.
|
|
577
|
+
* @task T13494
|
|
578
|
+
*/
|
|
579
|
+
persona: string;
|
|
566
580
|
/** Role the agent will execute as (`orchestrator` | `lead` | `worker`). @task T963 */
|
|
567
581
|
role: string;
|
|
568
582
|
/** Tier of the rendered prompt (0/1/2). @task T963 */
|
|
@@ -228,6 +228,11 @@ export interface ValidateGateParams {
|
|
|
228
228
|
* none; a gate without a fresh cached pass refuses with `E_GATE_NOT_CACHED`.
|
|
229
229
|
*/
|
|
230
230
|
noRun?: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* `cleo verify --run --req A,B` (T13486): run and cache only the typed gates
|
|
233
|
+
* with these REQ-IDs (comma-separated); the others are not run.
|
|
234
|
+
*/
|
|
235
|
+
req?: string;
|
|
231
236
|
}
|
|
232
237
|
export interface ValidateGateResult {
|
|
233
238
|
taskId: string;
|