@try-works/dsh-recursive-mode 0.1.3 → 0.1.5
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/cordis.patch.yml +15 -10
- package/lib/bootstrap.d.ts +9 -2
- package/lib/client/apply-guard.d.ts +14 -0
- package/lib/client/board.d.ts +5 -4
- package/lib/client/contract.d.ts +57 -24
- package/lib/client/host-api.d.ts +26 -0
- package/lib/client/index.d.ts +16 -9
- package/lib/client/inspector.d.ts +3 -3
- package/lib/client/slots.d.ts +4 -6
- package/lib/client/strip.d.ts +4 -3
- package/lib/client/use-live.d.ts +9 -0
- package/lib/client.js +252 -174
- package/lib/fs-intent.d.ts +34 -0
- package/lib/index.d.ts +12 -11
- package/lib/index.js +1008 -4507
- package/lib/init-templates.d.ts +41 -0
- package/lib/live-route.d.ts +69 -0
- package/lib/phase-rules.d.ts +34 -0
- package/lib/policy.d.ts +1 -1
- package/lib/runtime.d.ts +24 -17
- package/lib/snapshot.d.ts +11 -0
- package/package.json +4 -3
- package/preset/recursive/agent.cordis.yml +7 -4
- package/preset/recursive/preset.yml +1 -1
- package/references/agents-block.md +95 -0
- package/references/artifact-template.md +2574 -0
- package/references/bodies/claude.md +7 -0
- package/references/bodies/codex-agents.md +95 -0
- package/references/bodies/copilot.md +7 -0
- package/references/bodies/cursorrules.md +7 -0
- package/references/bodies/decisions.md +5 -0
- package/references/bodies/delegated-verification.md +49 -0
- package/references/bodies/memory-router.md +45 -0
- package/references/bodies/phase8-skill-memory.md +48 -0
- package/references/bodies/plans-bridge.md +43 -0
- package/references/bodies/recursive-agents-router.md +64 -0
- package/references/bodies/skill-discovery.md +51 -0
- package/references/bodies/skill-memory-router.md +42 -0
- package/references/bodies/state.md +5 -0
- package/references/bootstrap/RECURSIVE.md +2362 -0
- package/references/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
- package/references/scripts/lint-recursive-run.ps1 +25 -0
- package/references/scripts/lint-recursive-run.py +2870 -0
- package/references/scripts/recursive-closeout.ps1 +38 -0
- package/references/scripts/recursive-closeout.py +541 -0
- package/references/scripts/recursive-init.ps1 +515 -0
- package/references/scripts/recursive-init.py +356 -0
- package/references/scripts/recursive-lock.ps1 +26 -0
- package/references/scripts/recursive-lock.py +302 -0
- package/references/scripts/recursive-review-bundle.ps1 +58 -0
- package/references/scripts/recursive-review-bundle.py +503 -0
- package/references/scripts/recursive-router-cli-configure.ps1 +2 -0
- package/references/scripts/recursive-router-cli-configure.py +9 -0
- package/references/scripts/recursive-router-cli-init.ps1 +2 -0
- package/references/scripts/recursive-router-cli-init.py +9 -0
- package/references/scripts/recursive-router-cli-invoke.ps1 +2 -0
- package/references/scripts/recursive-router-cli-invoke.py +9 -0
- package/references/scripts/recursive-router-cli-probe.ps1 +2 -0
- package/references/scripts/recursive-router-cli-probe.py +9 -0
- package/references/scripts/recursive-router-cli-resolve.ps1 +2 -0
- package/references/scripts/recursive-router-cli-resolve.py +9 -0
- package/references/scripts/recursive-router-cli-validate.ps1 +2 -0
- package/references/scripts/recursive-router-cli-validate.py +9 -0
- package/references/scripts/recursive-router-configure.ps1 +27 -0
- package/references/scripts/recursive-router-configure.py +74 -0
- package/references/scripts/recursive-router-init.ps1 +17 -0
- package/references/scripts/recursive-router-init.py +29 -0
- package/references/scripts/recursive-router-invoke.ps1 +47 -0
- package/references/scripts/recursive-router-invoke.py +103 -0
- package/references/scripts/recursive-router-probe.ps1 +25 -0
- package/references/scripts/recursive-router-probe.py +44 -0
- package/references/scripts/recursive-router-resolve.ps1 +26 -0
- package/references/scripts/recursive-router-resolve.py +46 -0
- package/references/scripts/recursive-router-validate.ps1 +17 -0
- package/references/scripts/recursive-router-validate.py +27 -0
- package/references/scripts/recursive-status.ps1 +23 -0
- package/references/scripts/recursive-status.py +2124 -0
- package/references/scripts/recursive-subagent-action.ps1 +98 -0
- package/references/scripts/recursive-subagent-action.py +197 -0
- package/references/scripts/recursive-training-extract.ps1 +23 -0
- package/references/scripts/recursive-training-extract.py +99 -0
- package/references/scripts/recursive-training-grpo.ps1 +52 -0
- package/references/scripts/recursive-training-grpo.py +1341 -0
- package/references/scripts/recursive-training-loader.ps1 +78 -0
- package/references/scripts/recursive-training-loader.py +558 -0
- package/references/scripts/recursive-training-mcp.ps1 +29 -0
- package/references/scripts/recursive-training-mcp.py +261 -0
- package/references/scripts/recursive-training-phase8-trigger.ps1 +55 -0
- package/references/scripts/recursive-training-phase8-trigger.py +137 -0
- package/references/scripts/recursive-training-sync.ps1 +29 -0
- package/references/scripts/recursive-training-sync.py +183 -0
- package/references/scripts/recursive_phase_rules.py +367 -0
- package/references/scripts/recursive_router_cli_lib.py +2 -0
- package/references/scripts/recursive_router_lib.py +2282 -0
- package/references/scripts/verify-locks.ps1 +25 -0
- package/references/scripts/verify-locks.py +353 -0
- package/scripts/__pycache__/lint-recursive-run.cpython-314.pyc +0 -0
- package/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
- package/scripts/install-preset.cmd +7 -0
- package/scripts/install-preset.js +101 -0
- package/scripts/lint-recursive-run.py +7 -5
- package/scripts/test-recursive-mode-smoke.ts +29 -28
- package/src/bootstrap.ts +253 -39
- package/src/client/apply-guard.ts +23 -0
- package/src/client/board.tsx +0 -0
- package/src/client/contract.ts +72 -24
- package/src/client/host-api.ts +90 -0
- package/src/client/index.ts +22 -11
- package/src/client/inspector.tsx +7 -9
- package/src/client/slots.ts +70 -26
- package/src/client/strip.tsx +9 -9
- package/src/client/use-live.ts +52 -0
- package/src/fs-intent.ts +84 -0
- package/src/index.ts +100 -155
- package/src/init-templates.ts +276 -0
- package/src/live-route.ts +163 -0
- package/src/phase-rules.ts +262 -0
- package/src/policy.ts +29 -5
- package/src/runtime.ts +129 -77
- package/src/snapshot.ts +104 -0
- package/lib/client/node.d.ts +0 -71
- package/lib/events.d.ts +0 -173
- package/lib/projection.d.ts +0 -29
- package/src/client/node.ts +0 -156
- package/src/events.ts +0 -173
- package/src/projection.ts +0 -237
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live recursive board/strip route (SP2 R1): host HTTP + SSE serving the
|
|
3
|
+
* filesystem fold, keyed per-WORKSPACE. The board/strip read this instead of
|
|
4
|
+
* useProjection('recursive'); cold resume and every GET are a fresh fs read.
|
|
5
|
+
*
|
|
6
|
+
* Keying (creator steer): the client passes its session cwd verbatim; the HOST
|
|
7
|
+
* resolves the recursive control-plane root server-side (cwd == workspace root,
|
|
8
|
+
* cwd == subdir, cwd == repo root). Multiple sessions in one workspace collapse
|
|
9
|
+
* to one fold; a workspace switch shows the new root.
|
|
10
|
+
*
|
|
11
|
+
* R9 read-only: GET state + GET events only. No POST/action route; the client
|
|
12
|
+
* never mutates the host.
|
|
13
|
+
*
|
|
14
|
+
* Registration is mountOnce-global because apply() runs PER-SESSION while
|
|
15
|
+
* WebServer.register throws on a duplicate (kind, path). The first mount wins;
|
|
16
|
+
* later mounts no-op (the state is per-workspace, so per-session registration
|
|
17
|
+
* would be both wrong and a crash).
|
|
18
|
+
*/
|
|
19
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
20
|
+
import type { RecursiveProjection } from './types.ts'
|
|
21
|
+
|
|
22
|
+
/** API prefix the board/strip fetch. */
|
|
23
|
+
export const RECURSIVE_API_PREFIX = '/.recursive/api'
|
|
24
|
+
|
|
25
|
+
/** The host seam the route builder needs: root resolution + the fs fold + a revision. */
|
|
26
|
+
export interface RecursiveRouteHost {
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the recursive control-plane root. sessionId is PRIMARY (the host
|
|
29
|
+
* prefers the attached session header cwd — never trusts a client path); cwd
|
|
30
|
+
* is a fallback hint only while the session is hydrating / for headless callers.
|
|
31
|
+
*/
|
|
32
|
+
resolveRoot(sessionId: string | undefined, cwd: string): Promise<string | null>
|
|
33
|
+
/** Fresh filesystem fold for one workspace root (per-workspace state). */
|
|
34
|
+
snapshot(root: string): Promise<RecursiveProjection> | RecursiveProjection
|
|
35
|
+
/** Monotone revision; bump whenever the fs fold changes (SSE frame + state). */
|
|
36
|
+
revision(root: string): number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The state payload the board/strip read. */
|
|
40
|
+
export interface RecursiveStatePayload {
|
|
41
|
+
root: string | null
|
|
42
|
+
projection: RecursiveProjection
|
|
43
|
+
revision: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const HEARTBEAT_MS = 15_000
|
|
47
|
+
|
|
48
|
+
function json(res: ServerResponse, status: number, body: unknown): void {
|
|
49
|
+
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })
|
|
50
|
+
res.end(JSON.stringify(body))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Same-origin browser tripwire (NOT an authority check; the loopback socket is the real boundary). */
|
|
54
|
+
function browserMarker(req: IncomingMessage): boolean {
|
|
55
|
+
const headers = req.headers ?? {}
|
|
56
|
+
const site = headers['sec-fetch-site']
|
|
57
|
+
return site === 'same-origin' || typeof headers.origin === 'string'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Read a query param (URLSearchParams tolerates a missing/duplicate value). */
|
|
61
|
+
function queryOf(req: IncomingMessage, name: string): string {
|
|
62
|
+
const url = req.url ?? ''
|
|
63
|
+
const q = url.indexOf('?')
|
|
64
|
+
if (q < 0) return ''
|
|
65
|
+
return new URLSearchParams(url.slice(q + 1)).get(name) ?? ''
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Build the two read-only routes. Returns [state, events] in registration order.
|
|
70
|
+
* @param host - the resolved-root + fs-fold seam (the RecursiveRuntime adapter).
|
|
71
|
+
*/
|
|
72
|
+
export function makeRecursiveRoutes(host: RecursiveRouteHost): readonly { kind: 'exact'; path: string; handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void> }[] {
|
|
73
|
+
const state: { kind: 'exact'; path: string; handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void> } = {
|
|
74
|
+
kind: 'exact',
|
|
75
|
+
path: RECURSIVE_API_PREFIX + '/state',
|
|
76
|
+
handler: async (req, res) => {
|
|
77
|
+
if (req.method !== 'GET') { res.writeHead(405); res.end(); return }
|
|
78
|
+
if (!browserMarker(req)) { res.writeHead(403); res.end(); return }
|
|
79
|
+
const sessionId = queryOf(req, 'sessionId') || undefined
|
|
80
|
+
const cwd = queryOf(req, 'cwd')
|
|
81
|
+
const root = sessionId === undefined && cwd === '' ? null : await host.resolveRoot(sessionId, cwd)
|
|
82
|
+
if (root === null) {
|
|
83
|
+
json(res, 200, { root: null, projection: {}, revision: 0 } as RecursiveStatePayload)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
json(res, 200, { root, projection: await host.snapshot(root), revision: host.revision(root) } as RecursiveStatePayload)
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
const events: { kind: 'exact'; path: string; handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void> } = {
|
|
90
|
+
kind: 'exact',
|
|
91
|
+
path: RECURSIVE_API_PREFIX + '/events',
|
|
92
|
+
handler: async (req, res) => {
|
|
93
|
+
if (req.method !== 'GET') { res.writeHead(405); res.end(); return }
|
|
94
|
+
if (!browserMarker(req)) { res.writeHead(403); res.end(); return }
|
|
95
|
+
const sessionId = queryOf(req, 'sessionId') || undefined
|
|
96
|
+
const cwd = queryOf(req, 'cwd')
|
|
97
|
+
const root = sessionId === undefined && cwd === '' ? null : await host.resolveRoot(sessionId, cwd)
|
|
98
|
+
if (root === null) { res.writeHead(200, { 'content-type': 'text/event-stream; charset=utf-8', 'cache-control': 'no-cache', connection: 'keep-alive' }); res.write('data: {"root":null,"projection":{},"revision":0}\n\n'); res.end(); return }
|
|
99
|
+
res.writeHead(200, { 'content-type': 'text/event-stream; charset=utf-8', 'cache-control': 'no-cache', connection: 'keep-alive' })
|
|
100
|
+
const push = (): void => {
|
|
101
|
+
const payload = { root, projection: host.snapshot(root), revision: host.revision(root) }
|
|
102
|
+
res.write('data: ' + JSON.stringify(payload) + '\n\n')
|
|
103
|
+
}
|
|
104
|
+
const heartbeat = setInterval(() => { res.write(': ping\n\n') }, HEARTBEAT_MS)
|
|
105
|
+
const close = (): void => { clearInterval(heartbeat) }
|
|
106
|
+
req.once('close', close)
|
|
107
|
+
res.once('close', close)
|
|
108
|
+
await push()
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
return [state, events]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Register the routes at most ONCE per process. WebServer.register throws on a
|
|
116
|
+
* duplicate (kind, path), and apply() runs per-session, so later mounts MUST
|
|
117
|
+
* no-op. The registry rides a global symbol so two module instances of the same
|
|
118
|
+
* package share one verdict; the unmarker rides ctx.effect (cordis runs the
|
|
119
|
+
* effect callback immediately and treats its return as the disposer).
|
|
120
|
+
*/
|
|
121
|
+
const MOUNTED = Symbol.for('dsh-recursive-mode.mounted')
|
|
122
|
+
|
|
123
|
+
interface MountRegistry {
|
|
124
|
+
[MOUNTED]?: Set<string>
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function mountedSet(): Set<string> {
|
|
128
|
+
const registry = globalThis as MountRegistry
|
|
129
|
+
return (registry[MOUNTED] ??= new Set())
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface WebServerLike {
|
|
133
|
+
register(route: { kind: string; path: string; handler: unknown }): () => void
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* mountOnce wrapper for the route registration. Returns the disposer of the
|
|
138
|
+
* registration (or a no-op for a later mount).
|
|
139
|
+
* @param packageName - npm package identity (every install source shares it).
|
|
140
|
+
* @param makeRoutes - builds the routes (called once, on the first mount).
|
|
141
|
+
* @param webServer - the host webserver service (or a structural fake).
|
|
142
|
+
*/
|
|
143
|
+
export function mountRecursiveRoutesOnce(
|
|
144
|
+
packageName: string,
|
|
145
|
+
makeRoutes: () => readonly { kind: string; path: string; handler: unknown }[],
|
|
146
|
+
webServer: WebServerLike,
|
|
147
|
+
): () => void {
|
|
148
|
+
const mounted = mountedSet()
|
|
149
|
+
if (mounted.has(packageName)) return () => {}
|
|
150
|
+
mounted.add(packageName)
|
|
151
|
+
const disposers: Array<() => void> = []
|
|
152
|
+
try {
|
|
153
|
+
for (const route of makeRoutes()) disposers.push(webServer.register(route))
|
|
154
|
+
} catch (error) {
|
|
155
|
+
mounted.delete(packageName)
|
|
156
|
+
for (const dispose of disposers) dispose()
|
|
157
|
+
throw error
|
|
158
|
+
}
|
|
159
|
+
return () => {
|
|
160
|
+
for (const dispose of disposers) dispose()
|
|
161
|
+
mounted.delete(packageName)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase rules (R5): canonical parity port of lint-recursive-run.py's
|
|
3
|
+
* get_artifact_required_sections + workflow/audit constants. Single source of
|
|
4
|
+
* truth for per-phase required section headings and audit extras; consumed by
|
|
5
|
+
* initRun templates, renderRecursivePolicy, and the pre-step lint-rules
|
|
6
|
+
* injection. Values are byte-identical to the canonical linter (recursive-
|
|
7
|
+
* mode-audit-v2).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const CURRENT_WORKFLOW_PROFILE = 'recursive-mode-audit-v2'
|
|
11
|
+
export const STRICT_WORKFLOW_PROFILE = 'recursive-mode-audit-v1'
|
|
12
|
+
export const COMPAT_WORKFLOW_PROFILE = 'memory-phase8'
|
|
13
|
+
export const STRICT_WORKFLOW_PROFILES = new Set([CURRENT_WORKFLOW_PROFILE, STRICT_WORKFLOW_PROFILE])
|
|
14
|
+
|
|
15
|
+
export const LATE_PHASE_ARTIFACTS = ['06-decisions-update.md', '07-state-update.md', '08-memory-impact.md']
|
|
16
|
+
|
|
17
|
+
export const AUDITED_PHASE_FILES = new Set([
|
|
18
|
+
'01-as-is.md',
|
|
19
|
+
'01.5-root-cause.md',
|
|
20
|
+
'02-to-be-plan.md',
|
|
21
|
+
'03-implementation-summary.md',
|
|
22
|
+
'03.5-code-review.md',
|
|
23
|
+
'04-test-summary.md',
|
|
24
|
+
'06-decisions-update.md',
|
|
25
|
+
'07-state-update.md',
|
|
26
|
+
'08-memory-impact.md',
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
export const PRIOR_RECURSIVE_EVIDENCE_FILES = new Set([
|
|
30
|
+
'01-as-is.md',
|
|
31
|
+
'02-to-be-plan.md',
|
|
32
|
+
'04-test-summary.md',
|
|
33
|
+
'07-state-update.md',
|
|
34
|
+
'08-memory-impact.md',
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
export const DIFF_AUDITED_FILES = new Set([
|
|
38
|
+
'02-to-be-plan.md',
|
|
39
|
+
'03-implementation-summary.md',
|
|
40
|
+
'03.5-code-review.md',
|
|
41
|
+
'04-test-summary.md',
|
|
42
|
+
'06-decisions-update.md',
|
|
43
|
+
'07-state-update.md',
|
|
44
|
+
'08-memory-impact.md',
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
export const TRACEABILITY_REQUIRED_FILES = new Set([
|
|
48
|
+
'01-as-is.md',
|
|
49
|
+
'01.5-root-cause.md',
|
|
50
|
+
'02-to-be-plan.md',
|
|
51
|
+
'03-implementation-summary.md',
|
|
52
|
+
'03.5-code-review.md',
|
|
53
|
+
'04-test-summary.md',
|
|
54
|
+
'05-manual-qa.md',
|
|
55
|
+
'06-decisions-update.md',
|
|
56
|
+
'07-state-update.md',
|
|
57
|
+
'08-memory-impact.md',
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
export const AUDIT_REQUIRED_HEADINGS = [
|
|
61
|
+
'Audit Context',
|
|
62
|
+
'Effective Inputs Re-read',
|
|
63
|
+
'Earlier Phase Reconciliation',
|
|
64
|
+
'Subagent Contribution Verification',
|
|
65
|
+
'Worktree Diff Audit',
|
|
66
|
+
'Gaps Found',
|
|
67
|
+
'Repair Work Performed',
|
|
68
|
+
'Requirement Completion Status',
|
|
69
|
+
'Audit Verdict',
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
export const DIFF_BASIS_FIELDS = [
|
|
73
|
+
'Baseline type',
|
|
74
|
+
'Baseline reference',
|
|
75
|
+
'Comparison reference',
|
|
76
|
+
'Normalized baseline',
|
|
77
|
+
'Normalized comparison',
|
|
78
|
+
'Normalized diff command',
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
const SECTION_MAP: Record<string, string[]> = {
|
|
82
|
+
'00-worktree.md': [
|
|
83
|
+
'TODO',
|
|
84
|
+
'Directory Selection',
|
|
85
|
+
'Safety Verification',
|
|
86
|
+
'Worktree Creation',
|
|
87
|
+
'Main Branch Protection',
|
|
88
|
+
'Project Setup',
|
|
89
|
+
'Test Baseline Verification',
|
|
90
|
+
'Worktree Context',
|
|
91
|
+
'Diff Basis For Later Audits',
|
|
92
|
+
'Traceability',
|
|
93
|
+
'Coverage Gate',
|
|
94
|
+
'Approval Gate',
|
|
95
|
+
],
|
|
96
|
+
'00-requirements.md': [
|
|
97
|
+
'TODO',
|
|
98
|
+
'Requirements',
|
|
99
|
+
'Out of Scope',
|
|
100
|
+
'Constraints',
|
|
101
|
+
'Coverage Gate',
|
|
102
|
+
'Approval Gate',
|
|
103
|
+
],
|
|
104
|
+
'01-as-is.md': [
|
|
105
|
+
'TODO',
|
|
106
|
+
'Reproduction Steps (Novice-Runnable)',
|
|
107
|
+
'Current Behavior by Requirement',
|
|
108
|
+
'Source Requirement Inventory',
|
|
109
|
+
'Relevant Code Pointers',
|
|
110
|
+
'Known Unknowns',
|
|
111
|
+
'Evidence',
|
|
112
|
+
'Traceability',
|
|
113
|
+
'Coverage Gate',
|
|
114
|
+
'Approval Gate',
|
|
115
|
+
],
|
|
116
|
+
'01.5-root-cause.md': [
|
|
117
|
+
'TODO',
|
|
118
|
+
'Error Analysis',
|
|
119
|
+
'Reproduction Verification',
|
|
120
|
+
'Recent Changes Analysis',
|
|
121
|
+
'Evidence Gathering (Multi-Layer if applicable)',
|
|
122
|
+
'Data Flow Trace',
|
|
123
|
+
'Pattern Analysis',
|
|
124
|
+
'Hypothesis Testing',
|
|
125
|
+
'Root Cause Summary',
|
|
126
|
+
'Traceability',
|
|
127
|
+
'Coverage Gate',
|
|
128
|
+
'Approval Gate',
|
|
129
|
+
],
|
|
130
|
+
'02-to-be-plan.md': [
|
|
131
|
+
'TODO',
|
|
132
|
+
'Planned Changes by File',
|
|
133
|
+
'Requirement Mapping',
|
|
134
|
+
'Implementation Steps',
|
|
135
|
+
'Testing Strategy',
|
|
136
|
+
'Playwright Plan (if applicable)',
|
|
137
|
+
'Manual QA Scenarios',
|
|
138
|
+
'Idempotence and Recovery',
|
|
139
|
+
'Implementation Sub-phases',
|
|
140
|
+
'Plan Drift Check',
|
|
141
|
+
'Traceability',
|
|
142
|
+
'Coverage Gate',
|
|
143
|
+
'Approval Gate',
|
|
144
|
+
],
|
|
145
|
+
'03-implementation-summary.md': [
|
|
146
|
+
'TODO',
|
|
147
|
+
'Changes Applied',
|
|
148
|
+
'TDD Compliance Log',
|
|
149
|
+
'Plan Deviations',
|
|
150
|
+
'Implementation Evidence',
|
|
151
|
+
'Traceability',
|
|
152
|
+
'Coverage Gate',
|
|
153
|
+
'Approval Gate',
|
|
154
|
+
],
|
|
155
|
+
'03.5-code-review.md': [
|
|
156
|
+
'TODO',
|
|
157
|
+
'Review Scope',
|
|
158
|
+
'Plan Alignment Assessment',
|
|
159
|
+
'Code Quality Assessment',
|
|
160
|
+
'Issues Found',
|
|
161
|
+
'Verdict',
|
|
162
|
+
'Review Metadata',
|
|
163
|
+
'Traceability',
|
|
164
|
+
'Coverage Gate',
|
|
165
|
+
'Approval Gate',
|
|
166
|
+
],
|
|
167
|
+
'04-test-summary.md': [
|
|
168
|
+
'TODO',
|
|
169
|
+
'Pre-Test Implementation Audit',
|
|
170
|
+
'Environment',
|
|
171
|
+
'Execution Mode',
|
|
172
|
+
'Commands Executed (Exact)',
|
|
173
|
+
'Results Summary',
|
|
174
|
+
'Evidence and Artifacts',
|
|
175
|
+
'Failures and Diagnostics (if any)',
|
|
176
|
+
'Flake/Rerun Notes',
|
|
177
|
+
'Traceability',
|
|
178
|
+
'Coverage Gate',
|
|
179
|
+
'Approval Gate',
|
|
180
|
+
],
|
|
181
|
+
'05-manual-qa.md': [
|
|
182
|
+
'TODO',
|
|
183
|
+
'QA Execution Record',
|
|
184
|
+
'QA Scenarios and Results',
|
|
185
|
+
'Evidence and Artifacts',
|
|
186
|
+
'User Sign-Off',
|
|
187
|
+
'Traceability',
|
|
188
|
+
'Coverage Gate',
|
|
189
|
+
'Approval Gate',
|
|
190
|
+
],
|
|
191
|
+
'06-decisions-update.md': [
|
|
192
|
+
'TODO',
|
|
193
|
+
'Decisions Changes Applied',
|
|
194
|
+
'Rationale',
|
|
195
|
+
'Resulting Decision Entry',
|
|
196
|
+
'Traceability',
|
|
197
|
+
'Coverage Gate',
|
|
198
|
+
'Approval Gate',
|
|
199
|
+
],
|
|
200
|
+
'07-state-update.md': [
|
|
201
|
+
'TODO',
|
|
202
|
+
'State Changes Applied',
|
|
203
|
+
'Rationale',
|
|
204
|
+
'Resulting State Summary',
|
|
205
|
+
'Traceability',
|
|
206
|
+
'Coverage Gate',
|
|
207
|
+
'Approval Gate',
|
|
208
|
+
],
|
|
209
|
+
'08-memory-impact.md': [
|
|
210
|
+
'TODO',
|
|
211
|
+
'Diff Basis',
|
|
212
|
+
'Changed Paths Review',
|
|
213
|
+
'Affected Memory Docs',
|
|
214
|
+
'Run-Local Skill Usage Capture',
|
|
215
|
+
'Skill Memory Promotion Review',
|
|
216
|
+
'Uncovered Paths',
|
|
217
|
+
'Router and Parent Refresh',
|
|
218
|
+
'Final Status Summary',
|
|
219
|
+
'Traceability',
|
|
220
|
+
'Coverage Gate',
|
|
221
|
+
'Approval Gate',
|
|
222
|
+
],
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* get_artifact_required_sections(file_name, workflow_profile): canonical-parity
|
|
227
|
+
* required section headings for a phase artifact. Defaults to TODO + Coverage
|
|
228
|
+
* Gate + Approval Gate for unknown files. Audited phases in strict profiles get
|
|
229
|
+
* the audit headings appended (plus Prior Recursive Evidence Reviewed for the
|
|
230
|
+
* prior-evidence file set).
|
|
231
|
+
*/
|
|
232
|
+
export function getArtifactRequiredSections(fileName: string, workflowProfile: string = CURRENT_WORKFLOW_PROFILE): string[] {
|
|
233
|
+
const headings = [...(SECTION_MAP[fileName] ?? ['TODO', 'Coverage Gate', 'Approval Gate'])]
|
|
234
|
+
if (STRICT_WORKFLOW_PROFILES.has(workflowProfile) && AUDITED_PHASE_FILES.has(fileName)) {
|
|
235
|
+
headings.push(...AUDIT_REQUIRED_HEADINGS)
|
|
236
|
+
if (PRIOR_RECURSIVE_EVIDENCE_FILES.has(fileName)) {
|
|
237
|
+
headings.push('Prior Recursive Evidence Reviewed')
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return headings
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Compact lint-rules message for the pre-step injection (R5): this phase's
|
|
245
|
+
* required sections + phase-specific gate notes, derived from the canonical
|
|
246
|
+
* artifact-template sections. Kept short enough to prepend as a system-style
|
|
247
|
+
* reminder without bloating the step context.
|
|
248
|
+
*/
|
|
249
|
+
export function phaseLintRulesMessage(fileName: string, workflowProfile: string = CURRENT_WORKFLOW_PROFILE): string {
|
|
250
|
+
const sections = getArtifactRequiredSections(fileName, workflowProfile)
|
|
251
|
+
const lines = [
|
|
252
|
+
'<system-reminder>',
|
|
253
|
+
'Recursive-mode phase lint rules for THIS phase (' + fileName + '):',
|
|
254
|
+
'Required sections: ' + sections.join(' | '),
|
|
255
|
+
'Gates: Coverage: FAIL until all checkboxes pass; Approval: FAIL until user sign-off; lock only via recursive_lock (monotonic).',
|
|
256
|
+
'Audited phases: end with Audit: PASS before setting Coverage/Approval PASS; record Audit Context and Audit Verdict.',
|
|
257
|
+
'TDD (phase 3): declare TDD Mode: strict|pragmatic; strict requires RED + GREEN evidence paths.',
|
|
258
|
+
'QA (phase 5): declare QA Execution Mode: human|agent-operated|hybrid; human/hybrid need user sign-off.',
|
|
259
|
+
'</system-reminder>',
|
|
260
|
+
]
|
|
261
|
+
return lines.join('\n')
|
|
262
|
+
}
|
package/src/policy.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* recursive:policy prompt-section renderer (Phase C R5, PROPOSAL 8.4 Layer 3).
|
|
3
3
|
* Renders the CURRENT phase's contract from folded state + enforcement config:
|
|
4
4
|
* what must exist before advancing, which tools are denied/asked this phase,
|
|
5
|
-
* the strict/pragmatic TDD mode, the QA mode,
|
|
6
|
-
*
|
|
5
|
+
* the strict/pragmatic TDD mode, the QA mode, the lock chain, and (R5) the
|
|
6
|
+
* current phase's required sections + gate checklist — the same rules the
|
|
7
|
+
* machine gates enforce (no prompt/gate contradiction).
|
|
7
8
|
*/
|
|
8
9
|
import { existsSync } from 'node:fs'
|
|
9
10
|
import { join } from 'node:path'
|
|
@@ -12,11 +13,12 @@ import { foldRun } from './status.ts'
|
|
|
12
13
|
import type { RecursivePhaseState } from './lifecycle.ts'
|
|
13
14
|
import type { EnforcementConfig } from './enforcement.ts'
|
|
14
15
|
import { DEFAULT_ENFORCEMENT } from './enforcement.ts'
|
|
16
|
+
import { getArtifactRequiredSections, AUDITED_PHASE_FILES, CURRENT_WORKFLOW_PROFILE } from './phase-rules.ts'
|
|
15
17
|
|
|
16
18
|
export interface PolicyContext {
|
|
17
19
|
worktreeRoot: string
|
|
18
20
|
runId: string
|
|
19
|
-
folded
|
|
21
|
+
folded?: RecursivePhaseState | null
|
|
20
22
|
config?: EnforcementConfig
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -32,10 +34,12 @@ export function renderRecursivePolicy(context: PolicyContext | null): string {
|
|
|
32
34
|
// Derive the current phase from files (never trust an unchecked fold).
|
|
33
35
|
let currentPhase = folded?.phase ?? ''
|
|
34
36
|
let nextRequired = ''
|
|
37
|
+
let currentFile = ''
|
|
35
38
|
const status = existsSync(runDir) ? foldRun(runDir, context.runId) : null
|
|
36
39
|
if (status?.currentPhase) {
|
|
37
40
|
currentPhase = status.currentPhase.label + ' (' + status.currentPhase.status + ')'
|
|
38
41
|
nextRequired = status.currentPhase.phaseName + ' (' + status.currentPhase.key + ')'
|
|
42
|
+
currentFile = status.currentPhase.phaseName
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const strictness = (gate: EnforcementConfig[keyof EnforcementConfig]) => gate
|
|
@@ -51,6 +55,26 @@ export function renderRecursivePolicy(context: PolicyContext | null): string {
|
|
|
51
55
|
'- The control-plane root is resolved STRICTLY from this session workspace (never scan other workspaces).',
|
|
52
56
|
'- Scratch is disposable, git-ignored, and never citable as an Input.',
|
|
53
57
|
'- The workflow spec lives at /.recursive/RECURSIVE.md; bootstrap it when missing.',
|
|
54
|
-
]
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// R5: surface the current phase's required sections + gate checklist.
|
|
61
|
+
if (currentFile) {
|
|
62
|
+
const sections = getArtifactRequiredSections(currentFile, CURRENT_WORKFLOW_PROFILE)
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push('## This phase (' + currentFile + ') required sections (canonical lint):');
|
|
65
|
+
lines.push('- ' + sections.join(' | '));
|
|
66
|
+
lines.push('## This phase gate checklist:');
|
|
67
|
+
lines.push('- Coverage: FAIL until every checklist item is done; Approval: FAIL until sign-off; lock only via recursive_lock (monotonic).');
|
|
68
|
+
if (AUDITED_PHASE_FILES.has(currentFile)) {
|
|
69
|
+
lines.push('- Audited phase: end with Audit: PASS before setting Coverage/Approval PASS; record Audit Context + Audit Verdict.');
|
|
70
|
+
}
|
|
71
|
+
if (currentFile === '03-implementation-summary.md') {
|
|
72
|
+
lines.push('- TDD Mode: strict|pragmatic; strict requires RED + GREEN evidence paths.');
|
|
73
|
+
}
|
|
74
|
+
if (currentFile === '05-manual-qa.md') {
|
|
75
|
+
lines.push('- QA Execution Mode: human|agent-operated|hybrid; human/hybrid require user sign-off.');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
55
79
|
return lines.join('\n')
|
|
56
|
-
}
|
|
80
|
+
}
|