@try-works/dsh-recursive-mode 0.5.0 → 0.6.1
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 +21 -8
- package/lib/enforcement.d.ts +58 -0
- package/lib/index.js +597 -107
- package/lib/memory.d.ts +35 -2
- package/lib/phase-rules.d.ts +102 -0
- package/lib/policy-globs.d.ts +5 -0
- package/lib/training.d.ts +240 -2
- package/package.json +1 -1
- package/references/artifact-template.md +26 -61
- package/references/bodies/claude.md +1 -1
- package/references/bodies/copilot.md +1 -1
- package/references/bodies/cursorrules.md +4 -2
- package/references/bodies/memory-router.md +1 -1
- package/references/bodies/recursive-agents-router.md +4 -3
- package/references/bootstrap/RECURSIVE.md +21 -30
- package/src/bootstrap.ts +30 -14
- package/src/enforcement.ts +89 -5
- package/src/index.ts +795 -723
- package/src/memory.ts +50 -9
- package/src/phase-rules.ts +162 -1
- package/src/policy-globs.ts +28 -4
- package/src/runtime.ts +1962 -1945
- package/src/training.ts +634 -6
package/lib/memory.d.ts
CHANGED
|
@@ -18,7 +18,19 @@
|
|
|
18
18
|
* so the result is reproducible from the inputs alone.
|
|
19
19
|
*/
|
|
20
20
|
import { type FeedbackBook } from './memory-feedback.ts';
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* ⚠ `training` IS IN THIS LIST BECAUSE IT IS THE KIND THE PLUGIN'S OWN TRAINING PATH WRITES.
|
|
23
|
+
*
|
|
24
|
+
* The phase-8 trigger (`training.ts`) writes `memory/training/<task-type>.md` and advertises that path in
|
|
25
|
+
* `memory/MEMORY.md`; the shipped router (`references/bodies/memory-router.md`) tells an agent to load "the
|
|
26
|
+
* relevant docs under `/.recursive/memory/training/`". With `training` absent from this list the loader
|
|
27
|
+
* COULD NOT SEE THE SHARDS THE TRIGGER WROTE — the writer's own output was unreachable by the reader, so
|
|
28
|
+
* "the plane's next run scores it" (README §10) was not true of exactly the shards training produces.
|
|
29
|
+
*
|
|
30
|
+
* `incidents/` and `archive/` stay out deliberately: `archive/` is historical by the router's own definition,
|
|
31
|
+
* and widening retrieval to `incidents/` is a separate ranking decision that this change does not make.
|
|
32
|
+
*/
|
|
33
|
+
export declare const MEMORY_KINDS: readonly ["domains", "patterns", "episodes", "training", "skills"];
|
|
22
34
|
export type MemoryKind = (typeof MEMORY_KINDS)[number];
|
|
23
35
|
/** One memory note: a titled section of a memory document. */
|
|
24
36
|
export interface MemoryEntry {
|
|
@@ -95,7 +107,28 @@ export declare const MEMORY_PHASE_MATCH_WEIGHT = 2;
|
|
|
95
107
|
*/
|
|
96
108
|
export declare function entryAppliesTo(entry: MemoryEntry): string[];
|
|
97
109
|
/** The registry the loader starts from: the router, not the plane. */
|
|
98
|
-
export declare const MEMORY_INDEX_FILE = "memory/MEMORY.md";
|
|
110
|
+
export declare const MEMORY_INDEX_FILE = ".recursive/memory/MEMORY.md";
|
|
111
|
+
/**
|
|
112
|
+
* Where the plane may live under a workspace root, in PREFERENCE order.
|
|
113
|
+
*
|
|
114
|
+
* ⚠ THE MEASURED DEFECT THIS FIXES. `defaultMemoryList` used to join `memory/<kind>/` straight onto the
|
|
115
|
+
* root it was handed — i.e. `<root>/memory/` — and that directory EXISTS IN NO REAL WORKSPACE. `bootstrap.ts`
|
|
116
|
+
* scaffolds the plane at `<root>/.recursive/memory/`, `ts-lint.ts` lints it there, and the review bundle reads
|
|
117
|
+
* it there; this loader alone looked beside it. Measured live on a workspace whose `.recursive/memory/` was
|
|
118
|
+
* scaffolded and whose `<root>/memory/` did not exist: `selectMemory` reported "the memory plane is empty"
|
|
119
|
+
* over a plane that was there, every phase of every run — and the training shards `training.ts` writes were
|
|
120
|
+
* therefore unreachable by the loader that is supposed to score them.
|
|
121
|
+
*
|
|
122
|
+
* ⚠ PREFER, THEN FALL BACK — NEVER MERGE. This is the rule `readFeedback` already follows for its own moved
|
|
123
|
+
* sidecar (`memory-feedback.ts`: `FEEDBACK_FILE` then `LEGACY_FEEDBACK_FILE`): the current location wins, and
|
|
124
|
+
* the earlier one is consulted ONLY when the current one yields nothing, because two snapshots of one shard
|
|
125
|
+
* added together would count a shard twice and a duplicated shard would outrank a real one.
|
|
126
|
+
*
|
|
127
|
+
* ⚠ AND IT MAKES THE CALLER'S CONVENTION IRRELEVANT. Passing the workspace root resolves
|
|
128
|
+
* `<root>/.recursive/memory/`; passing `.recursive` itself resolves through the second entry. Both are
|
|
129
|
+
* accepted, which is what README §6 means by "using `.recursive` as the root is selected again".
|
|
130
|
+
*/
|
|
131
|
+
export declare const MEMORY_PLANE_BASES: readonly [".recursive/memory", "memory"];
|
|
99
132
|
/** Progressive disclosure defaults, matching the parent (`--max-docs` 3, `--max-items` 10). */
|
|
100
133
|
export declare const MAX_MEMORY_DOCS = 3;
|
|
101
134
|
export declare const MAX_MEMORY_ITEMS = 10;
|
package/lib/phase-rules.d.ts
CHANGED
|
@@ -10,6 +10,100 @@ export declare const DIFF_AUDITED_FILES: Set<string>;
|
|
|
10
10
|
export declare const TRACEABILITY_REQUIRED_FILES: Set<string>;
|
|
11
11
|
export declare const AUDIT_REQUIRED_HEADINGS: string[];
|
|
12
12
|
export declare const DIFF_BASIS_FIELDS: string[];
|
|
13
|
+
/**
|
|
14
|
+
* T40 — `.recursive/memory/`, the plane THIS plugin writes, and the phase-8 step that was prose.
|
|
15
|
+
*
|
|
16
|
+
* ⚠ WHY THIS EXISTS, MEASURED. Three completed runs in a live workspace left `.recursive/memory/`
|
|
17
|
+
* exactly as `bootstrap.ts` scaffolded it: `MEMORY.md` and the skill docs were still the
|
|
18
|
+
* bootstrap-created placeholders, while run 03's `08-memory-impact.md` had its
|
|
19
|
+
* "Write the durable ones to memory, with provenance" box TICKED and its `Inputs` line naming
|
|
20
|
+
* ANOTHER plugin's store (`memory_search` / `memory_status` is dsh-memory, not this plugin). So the
|
|
21
|
+
* phase declared its memory step done against a system this plugin does not own, and the plugin's
|
|
22
|
+
* own learning never activated — the owner's words: *"the agent should write to .recursive/memory/
|
|
23
|
+
* in phase 8, that's a hard requirement that needs to be enforced"*.
|
|
24
|
+
*
|
|
25
|
+
* ⚠ THE FIX IS A FACT, NOT A STRONGER SENTENCE. A prose step is ticked by the agent that would have
|
|
26
|
+
* had to do it, which is exactly what happened. What follows is the same requirement in the form the
|
|
27
|
+
* workflow can CHECK: a path under the plane, declared in the artifact, whose own text on disk
|
|
28
|
+
* carries this run's provenance (`Source-Runs`). "The run wrote its durable memory" then stops being
|
|
29
|
+
* a claim about the run's intentions and becomes a claim about files.
|
|
30
|
+
*
|
|
31
|
+
* ⚠ WHY IT IS NOT IN `SECTION_MAP`, deliberately: that map is byte-parity with the canonical
|
|
32
|
+
* linter's `get_artifact_required_sections` (`tests/phase-rules.parity.spec.ts` pins all twelve
|
|
33
|
+
* lists), and a section added there would be a parity break dressed up as a feature. The requirement
|
|
34
|
+
* rides on a section the canonical template ALREADY scaffolds — `## Affected Memory Docs` — plus a
|
|
35
|
+
* field the plane's own linter already requires, so the artifact shape stays canonical.
|
|
36
|
+
*/
|
|
37
|
+
export declare const PHASE8_MEMORY_ARTIFACT = "08-memory-impact.md";
|
|
38
|
+
/** The plane, repo-relative. The trailing slash is what every guard here tests for. */
|
|
39
|
+
export declare const MEMORY_PLANE_PREFIX = ".recursive/memory/";
|
|
40
|
+
/** The canonical section the declaration goes in — present in every scaffolded phase-8 artifact. */
|
|
41
|
+
export declare const PHASE8_MEMORY_SECTION = "Affected Memory Docs";
|
|
42
|
+
/**
|
|
43
|
+
* Where a durable doc may be filed, and the `Type` the memory-plane linter requires for it.
|
|
44
|
+
*
|
|
45
|
+
* ⚠ EVERY `dir` IS THE REAL PLANE, `.recursive/memory/…`, AND NOT A `memory/…` RELATIVE FORM. Measured:
|
|
46
|
+
* `bootstrap.ts` scaffolds the plane under `.recursive/`, `ts-lint.ts` lints it there, and the phase-8
|
|
47
|
+
* artifact cites it there — while the training trigger's own write seam joins its `memory/…` paths onto
|
|
48
|
+
* the workspace root, i.e. `<root>/memory/`, a directory that exists in no real workspace (the reader
|
|
49
|
+
* side of that defect was fixed in `memory.ts`; the writer side is `runtime.ts`'s seam and is reported,
|
|
50
|
+
* not edited). This table names the location a WRITE must land in, so it names the plane.
|
|
51
|
+
*
|
|
52
|
+
* ⚠ `skill` SHARES `pattern`'s Type ON PURPOSE: `/.recursive/memory/skills/patterns/` is where a
|
|
53
|
+
* promoted skill lesson ships (`bootstrap.ts` scaffolds three docs there), and the linter's allowed
|
|
54
|
+
* Types are `index|domain|pattern|incident|episode` — there is no `skill` Type to declare.
|
|
55
|
+
*/
|
|
56
|
+
export declare const MEMORY_DOC_LOCATIONS: {
|
|
57
|
+
readonly domain: {
|
|
58
|
+
readonly dir: ".recursive/memory/domains";
|
|
59
|
+
readonly type: "domain";
|
|
60
|
+
};
|
|
61
|
+
readonly pattern: {
|
|
62
|
+
readonly dir: ".recursive/memory/patterns";
|
|
63
|
+
readonly type: "pattern";
|
|
64
|
+
};
|
|
65
|
+
readonly incident: {
|
|
66
|
+
readonly dir: ".recursive/memory/incidents";
|
|
67
|
+
readonly type: "incident";
|
|
68
|
+
};
|
|
69
|
+
readonly episode: {
|
|
70
|
+
readonly dir: ".recursive/memory/episodes";
|
|
71
|
+
readonly type: "episode";
|
|
72
|
+
};
|
|
73
|
+
readonly skill: {
|
|
74
|
+
readonly dir: ".recursive/memory/skills/patterns";
|
|
75
|
+
readonly type: "pattern";
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
export type MemoryDocKind = keyof typeof MEMORY_DOC_LOCATIONS;
|
|
79
|
+
/** The field that makes a doc THIS run's. It is the phase-8 gate's entire discriminator. */
|
|
80
|
+
export declare const MEMORY_PROVENANCE_FIELD = "Source-Runs";
|
|
81
|
+
/** Where a run that believes it learned nothing can always record what the run was and what it cost. */
|
|
82
|
+
export declare const MEMORY_ALWAYS_AVAILABLE = ".recursive/memory/episodes/<run-id>.md";
|
|
83
|
+
/**
|
|
84
|
+
* The phase-8 obligation, as data — so the pre-step reminder, the `recursive_phase` payload and the
|
|
85
|
+
* lock-time gate all describe ONE rule instead of three paraphrases of it.
|
|
86
|
+
*/
|
|
87
|
+
export interface Phase8MemoryWriteRule {
|
|
88
|
+
/** The artifact that must declare the write. */
|
|
89
|
+
artifact: string;
|
|
90
|
+
/** The plane the write must land in, repo-relative. */
|
|
91
|
+
plane: string;
|
|
92
|
+
/** The section the declaration goes in. */
|
|
93
|
+
section: string;
|
|
94
|
+
/** The field a doc must carry for the write to count as THIS run's. */
|
|
95
|
+
provenanceField: string;
|
|
96
|
+
/** The doc a run with nothing else to record can always write. */
|
|
97
|
+
alwaysAvailable: string;
|
|
98
|
+
kinds: readonly MemoryDocKind[];
|
|
99
|
+
/** One sentence per line the pre-step reminder injects. */
|
|
100
|
+
summary: string;
|
|
101
|
+
/** The full instruction, for a caller that asks for the phase's rules. */
|
|
102
|
+
instruction: string;
|
|
103
|
+
}
|
|
104
|
+
export declare const PHASE8_MEMORY_WRITE_RULE: Phase8MemoryWriteRule;
|
|
105
|
+
/** The memory-write rule for an artifact, or null when that phase owes no memory write. */
|
|
106
|
+
export declare function phase8MemoryWriteRuleFor(fileName: string): Phase8MemoryWriteRule | null;
|
|
13
107
|
/**
|
|
14
108
|
* get_artifact_required_sections(file_name, workflow_profile): canonical-parity
|
|
15
109
|
* required section headings for a phase artifact. Defaults to TODO + Coverage
|
|
@@ -41,6 +135,14 @@ export interface PhaseRules {
|
|
|
41
135
|
audited: boolean;
|
|
42
136
|
tdd: boolean;
|
|
43
137
|
qa: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* T40 — the memory-write obligation for this phase, or null when it owes none.
|
|
140
|
+
*
|
|
141
|
+
* ⚠ IT IS PART OF THIS STRUCTURE, not a parallel table, because `runtime.phaseRules` spreads this
|
|
142
|
+
* object straight into the `recursive_phase` payload and the pre-step reminder is built from the
|
|
143
|
+
* same source: a rule that lives here is a rule the agent is actually told, in one place.
|
|
144
|
+
*/
|
|
145
|
+
memoryWrite: Phase8MemoryWriteRule | null;
|
|
44
146
|
}
|
|
45
147
|
export declare function phaseRulesFor(fileName: string, workflowProfile?: string): PhaseRules;
|
|
46
148
|
/**
|
package/lib/policy-globs.d.ts
CHANGED
|
@@ -28,6 +28,11 @@ export interface Decision {
|
|
|
28
28
|
* Extra facts a rule predicate may need. `args` is always the tool call's
|
|
29
29
|
* arguments; the run coordinates are present only when the caller has them
|
|
30
30
|
* (a guard call from a real session does; a pure policy unit test need not).
|
|
31
|
+
*
|
|
32
|
+
* ⚠ `runId` IS NOT DECORATION: the lock-order rule puts it in the refusal (`[run: <id>]`), so a refusal
|
|
33
|
+
* names the run whose tree it was decided from. It is the run the guard RESOLVED for this call — the run
|
|
34
|
+
* the call named when it named a usable one, otherwise the active run (see `resolveGuardRunId` in
|
|
35
|
+
* `enforcement.ts`) — never a second, independently-derived answer.
|
|
31
36
|
*/
|
|
32
37
|
export interface ToolPolicyContext {
|
|
33
38
|
args: Record<string, unknown>;
|
package/lib/training.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MemoryDocKind } from './phase-rules.ts';
|
|
1
2
|
/** The artifact whose lock marks a run as complete enough to learn from. */
|
|
2
3
|
export declare const PHASE8_ARTIFACT = "08-memory-impact.md";
|
|
3
4
|
/** The parent's exit codes, kept as names so a caller cannot mistake one failure for the other. */
|
|
@@ -94,8 +95,16 @@ export declare function runPhase8Trigger(root: string, runId: string, options?:
|
|
|
94
95
|
*
|
|
95
96
|
* ⚠ ONE ITEM PER RUN IS NAMED, so a reader can trace a learning back to the run that produced it —
|
|
96
97
|
* and the group is never presented as more evidence than it is.
|
|
98
|
+
*
|
|
99
|
+
* ⚠ T40 — AND IT NOW CARRIES THE PLANE'S METADATA HEADER, which it did not before. `memory/domains/
|
|
100
|
+
* <subsystem>.md` is a doc the memory-plane lint validates like any other, and this renderer wrote a
|
|
101
|
+
* bare `# Learnings:` heading — so the plugin's own cross-run extraction produced a doc its own
|
|
102
|
+
* `lint_memory_plane` FAILS for nine missing fields. The extraction was right and its output shape was
|
|
103
|
+
* wrong, which is exactly the kind of defect a write surface exists to prevent.
|
|
97
104
|
*/
|
|
98
|
-
export declare function renderGroupShard(group: TrainingGroup
|
|
105
|
+
export declare function renderGroupShard(group: TrainingGroup, options?: {
|
|
106
|
+
lastValidated?: string;
|
|
107
|
+
}): string;
|
|
99
108
|
/**
|
|
100
109
|
* T30 — the extractor, resolved from the environment.
|
|
101
110
|
*
|
|
@@ -182,7 +191,10 @@ export declare function updateMemoryRegistry(existing: string, entries: Readonly
|
|
|
182
191
|
* one training shard from another here. Named so a reader can disagree with it instead of discovering it.
|
|
183
192
|
*/
|
|
184
193
|
export declare function taskTypeShardPath(mode: TrainingGroup['mode']): string;
|
|
185
|
-
|
|
194
|
+
/** T40: same metadata-header reason as {@link renderGroupShard} — see the note there. */
|
|
195
|
+
export declare function renderTaskTypeShard(groups: readonly TrainingGroup[], options?: {
|
|
196
|
+
lastValidated?: string;
|
|
197
|
+
}): string;
|
|
186
198
|
/**
|
|
187
199
|
* T30 — turn the extractor's payload into items the grouping can use.
|
|
188
200
|
*
|
|
@@ -209,3 +221,229 @@ export declare function extractAndGroup(runner: (cmd: string) => ExtractorRun, e
|
|
|
209
221
|
items: TrainingItem[];
|
|
210
222
|
groups: TrainingGroup[];
|
|
211
223
|
};
|
|
224
|
+
/**
|
|
225
|
+
* T40 — THE PLUGIN'S OWN WAY TO WRITE `.recursive/memory/`.
|
|
226
|
+
*
|
|
227
|
+
* ⚠ WHY A WRITE SURFACE AND NOT ANOTHER PARAGRAPH OF INSTRUCTIONS. The memory plane's shape is not a
|
|
228
|
+
* convention an author can guess: a durable doc must carry nine metadata fields, its `Type` must be
|
|
229
|
+
* one of five, and the plane's own lint FAILs the whole run when one is missing. Measured against the
|
|
230
|
+
* live workspace, the plane held NOTHING but the bootstrap placeholders — so the practical choice was
|
|
231
|
+
* between an agent hand-rolling a doc that fails the plane lint and no doc at all. This renders the
|
|
232
|
+
* canonical shape, stamps the provenance the phase-8 gate reads back, replaces the shard's line in
|
|
233
|
+
* the registry, and refuses to write a doc its own linter would reject.
|
|
234
|
+
*
|
|
235
|
+
* ⚠ WHAT IS DELIBERATE, DECIDED HERE RATHER THAN LEFT TO A CALLER:
|
|
236
|
+
*
|
|
237
|
+
* - ATOMIC. `writeFileSync` to a sibling temp name, then a rename over the target. A memory doc is
|
|
238
|
+
* read by the loader and linted by the plane, and a torn write is worse than a missing one: a
|
|
239
|
+
* half-written doc is a FAILED lint that names a file nobody wrote, and there is no way to tell it
|
|
240
|
+
* from a real one afterwards.
|
|
241
|
+
* - PROVENANCE-CARRYING. `Source-Runs` names the run, and it is not decoration: it is the entire
|
|
242
|
+
* discriminator between "the run wrote its memory" and "the run cited a shard that already
|
|
243
|
+
* existed", which is the distinction the phase-8 gate turns on. It is also why the run id is
|
|
244
|
+
* stamped from the CALL rather than trusted per-doc.
|
|
245
|
+
* - DEDUPLICATED, IDEMPOTENTLY. Writing the same doc again from the same run is a no-op that SAYS
|
|
246
|
+
* so (`UNCHANGED`), because phase 8 closeout runs more than once — the training trigger's own
|
|
247
|
+
* reason for existing is the re-run — and a re-run must not duplicate a lesson or rewrite a doc
|
|
248
|
+
* byte-differently for no reason.
|
|
249
|
+
* - REFUSED WHEN THE ENTRY ALREADY EXISTS, unless the caller supersedes explicitly. Another run's
|
|
250
|
+
* shard is that run's evidence; silently overwriting it destroys provenance for a shard the plane
|
|
251
|
+
* could not restore. `supersede: true` is the deliberate path, and it ARCHIVES the previous
|
|
252
|
+
* revision under `memory/archive/` first — supersede, never delete, the same discipline the
|
|
253
|
+
* counters and the registry already follow.
|
|
254
|
+
* - AND NOTHING IS INVENTED. An empty scope or body, an unknown kind, a slug that is not a name, or
|
|
255
|
+
* a doc the linter would reject all come back as typed results with `written: false` — "zero
|
|
256
|
+
* writes" is a property a test asserts by listing the tree, not a promise in a comment.
|
|
257
|
+
*
|
|
258
|
+
* ⚠ THIS WRITES THIS PLUGIN'S MEMORY AND NOTHING ELSE. It never calls, wraps or delegates to another
|
|
259
|
+
* plugin's memory tools: the plane it touches is the `.recursive/memory/` tree this plugin scaffolds
|
|
260
|
+
* and lints, reached through this repo's own paths.
|
|
261
|
+
*/
|
|
262
|
+
export interface MemoryDocSpec {
|
|
263
|
+
kind: MemoryDocKind;
|
|
264
|
+
/** The run that wrote it. Stamped into `Source-Runs`, which is what the phase-8 gate reads back. */
|
|
265
|
+
runId: string;
|
|
266
|
+
/** Filename stem (`03-ambientcss-redesign`). Sanitised: a slug is a NAME, never a path. */
|
|
267
|
+
slug: string;
|
|
268
|
+
/** Defaults to the slug. */
|
|
269
|
+
title?: string;
|
|
270
|
+
/** What the doc is about, in one sentence — the field a later run ranks on. */
|
|
271
|
+
scope: string;
|
|
272
|
+
/** The lesson itself. Empty is REFUSED rather than padded: an invented lesson is not memory. */
|
|
273
|
+
body: string;
|
|
274
|
+
status?: string;
|
|
275
|
+
ownsPaths?: readonly string[];
|
|
276
|
+
watchPaths?: readonly string[];
|
|
277
|
+
tags?: readonly string[];
|
|
278
|
+
/** The commit the doc was validated against. Defaults to a token NAMING the run (see below). */
|
|
279
|
+
validatedAtCommit?: string;
|
|
280
|
+
/** ISO timestamp. Defaults to the moment of the write, which is measured rather than invented. */
|
|
281
|
+
lastValidated?: string;
|
|
282
|
+
parent?: string;
|
|
283
|
+
/**
|
|
284
|
+
* Runs that contributed BEFORE this one, kept in `Source-Runs` when this write replaces a doc.
|
|
285
|
+
* Set by the writer on a supersede/update; a caller may set it, and the union is what keeps a
|
|
286
|
+
* replaced revision's history readable.
|
|
287
|
+
*/
|
|
288
|
+
priorRuns?: readonly string[];
|
|
289
|
+
}
|
|
290
|
+
export interface MemoryWriteOptions {
|
|
291
|
+
/**
|
|
292
|
+
* Replace a shard ANOTHER RUN owns, by archiving it under `memory/archive/` first. Default false:
|
|
293
|
+
* the write is REFUSED and the refusal names both remedies.
|
|
294
|
+
*/
|
|
295
|
+
supersede?: boolean;
|
|
296
|
+
/** The clock, injected so the caller (and a test) decides what "now" is. */
|
|
297
|
+
now?: () => Date;
|
|
298
|
+
}
|
|
299
|
+
export type MemoryWriteCode = 'WRITTEN' | 'UPDATED' | 'UNCHANGED' | 'REFUSED' | 'INVALID';
|
|
300
|
+
export interface MemoryWriteResult {
|
|
301
|
+
code: MemoryWriteCode;
|
|
302
|
+
/** Repo-relative path (`memory/episodes/<slug>.md`); EMPTY only when no path could be resolved. */
|
|
303
|
+
path: string;
|
|
304
|
+
/** True only for `WRITTEN` and `UPDATED`. */
|
|
305
|
+
written: boolean;
|
|
306
|
+
/** Where a superseded revision was archived, when that happened. */
|
|
307
|
+
archived: string | null;
|
|
308
|
+
/** Always says what happened — a silent refusal is a lost work item. */
|
|
309
|
+
reason: string;
|
|
310
|
+
}
|
|
311
|
+
/** `2026-10-10T08:39:59Z` — the lock fields' own timestamp shape, and the docs' `Last-Validated` one. */
|
|
312
|
+
export declare function isoSeconds(now?: Date): string;
|
|
313
|
+
/** A slug is a FILE NAME: no separator, no traversal, no extension, no leading dot. */
|
|
314
|
+
export declare function sanitizeMemorySlug(slug: string): string;
|
|
315
|
+
/** The repo-relative path a doc gets, or null when the kind or the slug cannot name one. */
|
|
316
|
+
export declare function memoryDocRelativePath(kind: MemoryDocKind, slug: string): string | null;
|
|
317
|
+
/**
|
|
318
|
+
* The metadata header every durable doc carries: the nine fields `lint_memory_doc` requires, in the
|
|
319
|
+
* order the SHIPPED docs use them (`Owns-Paths:` / `Watch-Paths:` / `Tags:` stand bare when empty,
|
|
320
|
+
* which is what the workspace's own promoted docs do and what `has_header_field` accepts).
|
|
321
|
+
*
|
|
322
|
+
* ⚠ A BACKTICK INSIDE A FIELD VALUE IS REPLACED, NOT ESCAPED, because these values are read back by
|
|
323
|
+
* a line-based field reader: a stray backtick would end the value early and leave the rest of the
|
|
324
|
+
* sentence in the doc as if it were a field.
|
|
325
|
+
*/
|
|
326
|
+
export declare function renderMemoryMetadata(input: {
|
|
327
|
+
type: string;
|
|
328
|
+
status: string;
|
|
329
|
+
scope: string;
|
|
330
|
+
sourceRuns: readonly string[];
|
|
331
|
+
validatedAtCommit: string;
|
|
332
|
+
lastValidated: string;
|
|
333
|
+
ownsPaths?: readonly string[];
|
|
334
|
+
watchPaths?: readonly string[];
|
|
335
|
+
tags?: readonly string[];
|
|
336
|
+
parent?: string;
|
|
337
|
+
}): string;
|
|
338
|
+
/**
|
|
339
|
+
* Render the whole doc: the canonical header, then the title, then the lesson.
|
|
340
|
+
*
|
|
341
|
+
* ⚠ `Validated-At-Commit` DEFAULTS TO A TOKEN THAT NAMES THE RUN, NOT A SHA. A run writing its own
|
|
342
|
+
* lesson has no commit to be validated against yet, and a placeholder SHA would be a fabricated fact
|
|
343
|
+
* in the one field a reader uses to decide whether the lesson still holds. The shipped generic docs
|
|
344
|
+
* use the same convention (`generic-repository-guidance`).
|
|
345
|
+
*/
|
|
346
|
+
export declare function renderMemoryDoc(spec: MemoryDocSpec, options?: {
|
|
347
|
+
lastValidated?: string;
|
|
348
|
+
}): string;
|
|
349
|
+
/**
|
|
350
|
+
* The problems that would make the memory plane FAIL this doc — checked with the LINTER'S own field
|
|
351
|
+
* list, allowed Types and allowed Statuses, so this cannot accept a doc the plane rejects.
|
|
352
|
+
*/
|
|
353
|
+
export declare function memoryDocProblems(content: string): string[];
|
|
354
|
+
/**
|
|
355
|
+
* The values of a list field (`Source-Runs:`), inline or as bullets.
|
|
356
|
+
*
|
|
357
|
+
* ⚠ THE BLOCK ENDS AT THE FIRST BLANK LINE, HEADING OR NEW FIELD, and that strictness is the point:
|
|
358
|
+
* the alternative is a parser that reads an unrelated bullet list further down the document as
|
|
359
|
+
* provenance — and provenance is the one thing here that must never be guessed.
|
|
360
|
+
*/
|
|
361
|
+
export declare function parseMemoryListField(content: string, fieldName: string): string[];
|
|
362
|
+
/** The runs a doc's `Source-Runs` names. EXACT matches: `run-1` is not `run-10`. */
|
|
363
|
+
export declare function memoryDocProvenance(content: string): string[];
|
|
364
|
+
/** The task-type token a shard's registry line carries, from the directory it lives in. */
|
|
365
|
+
export declare function taskTypeForMemoryPath(path: string): string;
|
|
366
|
+
/**
|
|
367
|
+
* Write ONE durable doc, atomically, with provenance — and refuse rather than guess.
|
|
368
|
+
*
|
|
369
|
+
* The four decided behaviours (see {@link MemoryDocSpec}'s block comment): atomic, provenance-carrying,
|
|
370
|
+
* idempotent for the same run, and refused when another run owns the path unless `supersede` is
|
|
371
|
+
* explicit — in which case the previous revision is ARCHIVED first, so nothing is ever deleted.
|
|
372
|
+
*/
|
|
373
|
+
export declare function writeMemoryDoc(root: string, spec: MemoryDocSpec, options?: MemoryWriteOptions): MemoryWriteResult;
|
|
374
|
+
export interface RunMemoryWriteResult {
|
|
375
|
+
/** Repo-relative paths actually written (`WRITTEN` or `UPDATED`), in order. */
|
|
376
|
+
writes: string[];
|
|
377
|
+
results: MemoryWriteResult[];
|
|
378
|
+
/** The registry write (`memory/MEMORY.md`), or null when nothing was written. */
|
|
379
|
+
registry: MemoryWriteResult | null;
|
|
380
|
+
/** What happened, INCLUDING the failure sentence — a caller must not have to infer it. */
|
|
381
|
+
reason: string;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* T40 — THE PHASE-8 CALL: write this run's durable docs and register them.
|
|
385
|
+
*
|
|
386
|
+
* ⚠ THE RUN ID COMES FROM THE ARGUMENT, NOT FROM EACH SPEC. A spec that named a different run would
|
|
387
|
+
* write provenance the phase-8 gate then refuses — a doc claiming a write this run did not make —
|
|
388
|
+
* and the caller would be left holding a file that blocks its own lock. One run per call removes
|
|
389
|
+
* that possibility instead of documenting it.
|
|
390
|
+
*
|
|
391
|
+
* ⚠ THE REGISTRY IS REFRESHED ONLY WHEN SOMETHING WAS WRITTEN, because the failure paths must write
|
|
392
|
+
* NOTHING (the module's standing contract) and because a registry refreshed over an unchanged plane
|
|
393
|
+
* is a claim that something changed. `updateMemoryRegistry` is reused rather than reimplemented: a
|
|
394
|
+
* shard's line is REPLACED, never duplicated, and a shard is never removed.
|
|
395
|
+
*
|
|
396
|
+
* ⚠ AND IT IS THE PLANE'S REGISTRY, `.recursive/memory/MEMORY.md` — the file `MEMORY_INDEX_FILE`
|
|
397
|
+
* names and the same one `bootstrap.ts` marker-upserts. The trigger's older `memory/MEMORY.md` is read
|
|
398
|
+
* as a FALLBACK so a registry the previous call site wrote is not silently discarded, and it is never
|
|
399
|
+
* written to: two registries in one workspace would be two answers to "what does this plane hold".
|
|
400
|
+
*/
|
|
401
|
+
export declare function writeRunMemory(root: string, runId: string, specs: readonly MemoryDocSpec[], options?: MemoryWriteOptions): RunMemoryWriteResult;
|
|
402
|
+
/** Every `.recursive/memory/**` path a text declares, in the absolute or repo-relative spelling. */
|
|
403
|
+
export declare function phase8MemoryRefs(text: string): string[];
|
|
404
|
+
export interface Phase8MemoryEvidence {
|
|
405
|
+
ok: boolean;
|
|
406
|
+
/** Paths under the plane the artifact declares, repo-relative to the repo root (sorted, deduped). */
|
|
407
|
+
declared: string[];
|
|
408
|
+
/** Declared paths that exist on disk. */
|
|
409
|
+
existing: string[];
|
|
410
|
+
/** Declared paths whose own text carries THIS run's provenance: the writes that COUNT. */
|
|
411
|
+
written: string[];
|
|
412
|
+
reason: string;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* T40 — THE CHECKABLE FACT BEHIND "the run wrote its durable memory".
|
|
416
|
+
*
|
|
417
|
+
* Three conditions, and each one exists because of a way the claim could be made without the work
|
|
418
|
+
* being done: the artifact DECLARES a path under the plane (not prose about memory in general); the
|
|
419
|
+
* path EXISTS (a declaration is not a write); and the doc on disk carries `Source-Runs` naming THIS
|
|
420
|
+
* run (a shard the run merely read, or one an earlier run wrote, is not this run's memory).
|
|
421
|
+
*
|
|
422
|
+
* ⚠ WHAT IT CANNOT TELL, stated rather than hidden: WHEN the doc was written. A run that wrote a
|
|
423
|
+
* memory doc before phase 8 and cites it here passes — and the phase 6/7 baselines deny memory-plane
|
|
424
|
+
* writes outright (`phaseBaselineRules`), so within this workflow the only phase that can produce
|
|
425
|
+
* such a doc is 8. The check is about the FACT existing at lock time, not about the clock.
|
|
426
|
+
*/
|
|
427
|
+
export declare function phase8MemoryEvidence(root: string, runId: string, artifactText: string, readText?: (path: string) => string | null): Phase8MemoryEvidence;
|
|
428
|
+
/**
|
|
429
|
+
* T40 — THE REFUSAL, as a sentence, or null when the run may lock.
|
|
430
|
+
*
|
|
431
|
+
* ⚠ THE MESSAGE NAMES THE REMEDY, not only the fault. A refusal that says "no memory doc" leaves the
|
|
432
|
+
* agent to guess a format it cannot guess (nine fields, five allowed Types, a provenance list), which
|
|
433
|
+
* is how the step became a ticked box in the first place.
|
|
434
|
+
*/
|
|
435
|
+
export declare function phase8MemoryRefusal(root: string, runId: string, artifactText: string, readText?: (path: string) => string | null): string | null;
|
|
436
|
+
/**
|
|
437
|
+
* T40 — THE LOCK-TIME ENTRY POINT: the refusal for the artifact being locked, or null.
|
|
438
|
+
*
|
|
439
|
+
* ⚠ THIS EXISTS SO THE GATE IS ONE LINE AT ITS CALL SITE. The decision (declared → exists → carries
|
|
440
|
+
* this run's provenance) belongs in this module with the write surface that produces it; `lockArtifact`
|
|
441
|
+
* should have to say only WHICH artifact it is locking, not how a memory doc is recognised. A caller
|
|
442
|
+
* that has to reproduce the rule would be a second copy of it.
|
|
443
|
+
*
|
|
444
|
+
* ⚠ AND A MISSING ARTIFACT IS NOT THIS GATE'S REFUSAL. `lockArtifact` already refuses an absent
|
|
445
|
+
* artifact before any gate can run, and returning a memory refusal for a file that does not exist
|
|
446
|
+
* would replace "the artifact is missing" with a sentence about memory — a misleading diagnosis in
|
|
447
|
+
* exchange for nothing.
|
|
448
|
+
*/
|
|
449
|
+
export declare function phase8MemoryLockRefusal(root: string, runId: string, artifact: string): string | null;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@try-works/dsh-recursive-mode",
|
|
3
3
|
"description": "recursive-mode workflow as a DeepSeek Harness bundle: RecursiveRuntime service + 13 recursive_* tools (recursive_status, recursive_init, recursive_lock, recursive_lint, recursive_closeout, recursive_scratch, recursive_worktree, recursive_phase, recursive_audit_team, recursive_review, recursive_delegate, recursive_ask, recursive_preview)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -2437,49 +2437,32 @@ Approval: PASS
|
|
|
2437
2437
|
|
|
2438
2438
|
Before locking (or when a lock verification fails unexpectedly), lint the run artifacts for required header fields, required section headings, and TODO completion rules:
|
|
2439
2439
|
|
|
2440
|
-
```
|
|
2441
|
-
#
|
|
2442
|
-
|
|
2443
|
-
#
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
python3 ./.agents/skills/recursive-mode/scripts/lint-recursive-run.py --run-id "<run-id>" --strict
|
|
2452
|
-
python3 ./.recursive/scripts/lint-recursive-run.py --run-id "<run-id>" --strict
|
|
2453
|
-
|
|
2454
|
-
# Lint specific run
|
|
2455
|
-
.\.agents\skills\recursive-mode\scripts\lint-recursive-run.ps1 -RunId "<run-id>"
|
|
2456
|
-
# Or, when running from this repo:
|
|
2457
|
-
.\scripts\lint-recursive-run.ps1 -RunId "<run-id>"
|
|
2458
|
-
|
|
2459
|
-
# Treat WARN as FAIL
|
|
2460
|
-
.\.agents\skills\recursive-mode\scripts\lint-recursive-run.ps1 -RunId "<run-id>" -Strict
|
|
2461
|
-
.\scripts\lint-recursive-run.ps1 -RunId "<run-id>" -Strict
|
|
2440
|
+
```text
|
|
2441
|
+
# One artifact (defaults to the run's current phase). `mode: "summary"` returns a few
|
|
2442
|
+
# findings plus the true totals; `full` (default) returns up to 200 per list. When a list
|
|
2443
|
+
# is clipped, `elided` says how many and how to see the rest.
|
|
2444
|
+
recursive_lint { "runId": "<run-id>", "artifact": "<artifact>.md", "mode": "summary" }
|
|
2445
|
+
|
|
2446
|
+
# Treat WARN as FAIL is not a flag here: `errors` are the failures, `warnings` are not,
|
|
2447
|
+
# and both lists come back with their true `failCount`/`warnCount`.
|
|
2448
|
+
|
|
2449
|
+
# The same read, from the slash-command surface
|
|
2450
|
+
/recursive status <run-id>
|
|
2462
2451
|
```
|
|
2463
2452
|
|
|
2453
|
+
This plugin is TypeScript and runs the linter in-process: there is no `lint-recursive-run.py`/`.ps1` to call, and no `.recursive/scripts/` to call it from.
|
|
2454
|
+
|
|
2464
2455
|
## Locking Commands
|
|
2465
2456
|
|
|
2466
2457
|
Preferred:
|
|
2467
2458
|
|
|
2468
|
-
```
|
|
2469
|
-
#
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
python ./.recursive/scripts/recursive-lock.py --run-id "<run-id>" --artifact "<artifact>.md"
|
|
2473
|
-
python3 ./.agents/skills/recursive-mode/scripts/recursive-lock.py --run-id "<run-id>" --artifact "<artifact>.md"
|
|
2474
|
-
python3 ./.recursive/scripts/recursive-lock.py --run-id "<run-id>" --artifact "<artifact>.md"
|
|
2475
|
-
|
|
2476
|
-
# PowerShell
|
|
2477
|
-
.\.agents\skills\recursive-mode\scripts\recursive-lock.ps1 -RunId "<run-id>" -Artifact "<artifact>.md"
|
|
2478
|
-
# Or, when running from this repo:
|
|
2479
|
-
.\scripts\recursive-lock.ps1 -RunId "<run-id>" -Artifact "<artifact>.md"
|
|
2459
|
+
```text
|
|
2460
|
+
# Lock one artifact once its gates pass. It refuses a lock whose gates or whose earlier
|
|
2461
|
+
# phases are unmet, and writes Status: LOCKED, LockedAt and LockHash.
|
|
2462
|
+
recursive_lock { "runId": "<run-id>", "artifact": "<artifact>.md" }
|
|
2480
2463
|
```
|
|
2481
2464
|
|
|
2482
|
-
The lock
|
|
2465
|
+
The lock tool is the primary supported path. It refuses to lock artifacts whose required gates or lint-critical structure are still invalid.
|
|
2483
2466
|
|
|
2484
2467
|
Manual fallback for hash computation only:
|
|
2485
2468
|
|
|
@@ -2521,34 +2504,16 @@ sed '/^LockHash:/d' .recursive/run/<run-id>/<artifact>.md | tr -d '\r' | sha256s
|
|
|
2521
2504
|
|
|
2522
2505
|
### Automated Verification
|
|
2523
2506
|
|
|
2524
|
-
Use the
|
|
2507
|
+
Use the plugin's own tool to verify all locks in a run — there is no verifier script:
|
|
2525
2508
|
|
|
2526
|
-
```
|
|
2527
|
-
#
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
python ./.recursive/scripts/verify-locks.py --run-id "<run-id>"
|
|
2531
|
-
python3 ./.agents/skills/recursive-mode/scripts/verify-locks.py --run-id "<run-id>"
|
|
2532
|
-
python3 ./.recursive/scripts/verify-locks.py --run-id "<run-id>"
|
|
2533
|
-
|
|
2534
|
-
# Fix incorrect hashes (use with caution)
|
|
2535
|
-
python ./.agents/skills/recursive-mode/scripts/verify-locks.py --run-id "<run-id>" --fix
|
|
2536
|
-
# Or, when running from this repo:
|
|
2537
|
-
python ./.recursive/scripts/verify-locks.py --run-id "<run-id>" --fix
|
|
2538
|
-
python3 ./.agents/skills/recursive-mode/scripts/verify-locks.py --run-id "<run-id>" --fix
|
|
2539
|
-
python3 ./.recursive/scripts/verify-locks.py --run-id "<run-id>" --fix
|
|
2540
|
-
```
|
|
2509
|
+
```text
|
|
2510
|
+
# One run: the phase table and per-artifact lock state. A LockHash that no longer matches
|
|
2511
|
+
# its content is reported as TAMPERED, named against the receipt it was locked under.
|
|
2512
|
+
recursive_status { "runId": "<run-id>" }
|
|
2541
2513
|
|
|
2542
|
-
|
|
2543
|
-
#
|
|
2544
|
-
|
|
2545
|
-
# Or, when running from this repo:
|
|
2546
|
-
.\scripts\verify-locks.ps1 -RunId "<run-id>"
|
|
2547
|
-
|
|
2548
|
-
# Fix incorrect hashes (use with caution)
|
|
2549
|
-
.\.agents\skills\recursive-mode\scripts\verify-locks.ps1 -RunId "<run-id>" -Fix
|
|
2550
|
-
# Or, when running from this repo:
|
|
2551
|
-
.\scripts\verify-locks.ps1 -RunId "<run-id>" -Fix
|
|
2514
|
+
# There is deliberately no `--fix`. Re-hashing a changed artifact would erase the only
|
|
2515
|
+
# evidence that it changed, so the mismatch is reported and the decision (restore the
|
|
2516
|
+
# content, or record an addendum) stays with the agent.
|
|
2552
2517
|
```
|
|
2553
2518
|
|
|
2554
2519
|
### Manual Verification
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
- Canonical repository memory lives under `/.recursive/memory/`.
|
|
4
4
|
- Read `/.recursive/memory/MEMORY.md` before loading any other memory docs.
|
|
5
5
|
- Load only the memory docs relevant to the current task.
|
|
6
|
-
- When repository experiential memory may help,
|
|
6
|
+
- When repository experiential memory may help, load it through the plugin's own TS loader: `recursive_phase` returns the prior-run memory shards relevant to this run and phase (with the reason when none matched), and `/recursive memory "<task>" [--phase <nn>]` prints the same selection with its score components. There is no script to run and no `.recursive/scripts/` step — retrieval is in-process.
|
|
7
7
|
- Treat this file as a pointer only; the canonical memory store remains `/.recursive/memory/`.
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
- Canonical repository memory lives under `/.recursive/memory/`.
|
|
4
4
|
- Read `/.recursive/memory/MEMORY.md` before loading any other memory docs.
|
|
5
5
|
- Load only the memory docs relevant to the current task.
|
|
6
|
-
- When repository experiential memory may help,
|
|
6
|
+
- When repository experiential memory may help, load it through the plugin's own TS loader: `recursive_phase` returns the prior-run memory shards relevant to this run and phase (with the reason when none matched), and `/recursive memory "<task>" [--phase <nn>]` prints the same selection with its score components. There is no script to run and no `.recursive/scripts/` step — retrieval is in-process.
|
|
7
7
|
- Treat this file as a pointer only; the canonical memory store remains `/.recursive/memory/`.
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
# Canonical repository memory lives under `/.recursive/memory/`.
|
|
3
3
|
# Read `/.recursive/memory/MEMORY.md` before loading any other memory docs.
|
|
4
4
|
# Load only the memory docs relevant to the current task.
|
|
5
|
-
# When repository experiential memory may help,
|
|
6
|
-
#
|
|
5
|
+
# When repository experiential memory may help, load it through the plugin's own TS loader: the
|
|
6
|
+
# `recursive_phase` tool returns the prior-run memory shards relevant to this run and phase (with the
|
|
7
|
+
# reason when none matched), and `/recursive memory "<task>" [--phase <nn>]` prints the same selection
|
|
8
|
+
# with its score components. There is no script to run and no `.recursive/scripts/` step.
|
|
7
9
|
# This file is only a pointer surface; the canonical memory store remains `/.recursive/memory/`.
|
|
@@ -16,7 +16,7 @@ Control-plane docs are not memory docs:
|
|
|
16
16
|
- Read this file before loading any other memory docs.
|
|
17
17
|
- Load only the memory docs relevant to the current task.
|
|
18
18
|
- If the task may benefit from prior recursive-mode experiential learnings, use this index to identify the relevant docs under `/.recursive/memory/training/` and `/.recursive/memory/domains/`.
|
|
19
|
-
- The
|
|
19
|
+
- The plugin loads this plane **in-process, in TypeScript**: the `recursive_phase` tool returns the shards that match the run and the phase in play, and `/recursive memory "<task>" [--phase <nn>]` prints the same selection with the score component behind every shard and a reason for every shard it left out. There is no `recursive-training-loader`/`recursive-training-sync` script to run and no `.recursive/scripts/` helper in this plugin.
|
|
20
20
|
- If the task plans delegated review, subagent help, review bundles, smoke-harness portability work, or capability-sensitive execution, read `/.recursive/memory/skills/SKILLS.md` and then load the relevant skill-memory shards.
|
|
21
21
|
- If Phase 8 will need to promote durable lessons, first capture run-local skill usage in the run artifact and only then promote generalized conclusions into skill-memory shards.
|
|
22
22
|
- Prefer `Status: CURRENT` docs for planning and execution.
|
|
@@ -48,10 +48,11 @@ It exists to reduce blind doc-by-doc scanning. It is not a second workflow spec.
|
|
|
48
48
|
- the installed `recursive-router`, `recursive-subagent`, and `recursive-review-bundle` skills
|
|
49
49
|
- Working on memory behavior:
|
|
50
50
|
- `/.recursive/memory/MEMORY.md`
|
|
51
|
-
- the
|
|
52
|
-
-
|
|
53
|
-
-
|
|
51
|
+
- the memory shards themselves: `/.recursive/memory/domains/`, `/.recursive/memory/training/`, and the rest of the registry in `MEMORY.md`
|
|
52
|
+
- the plugin's **TS** memory surfaces — `recursive_phase` (injects the shards that match this run and phase) and `/recursive memory "<task>" [--phase <nn>]` (the same selection, printed with its score components)
|
|
53
|
+
- `recursive_closeout --phase 08` for the run-close training trigger, which runs in-process; its extractor is the operator environment variable `RECURSIVE_TRAINING_EXTRACTOR_CMD`
|
|
54
54
|
- `/.recursive/memory/skills/SKILLS.md`
|
|
55
|
+
- ⚠ there is no `.recursive/scripts/` helper and no Python anywhere in this plugin — lint, lock, status, closeout and retrieval all run in-process, so a script path is not a route to any of them
|
|
55
56
|
|
|
56
57
|
## Non-Canonical Bridges
|
|
57
58
|
|