@ngockhoale/ukit 2.6.6 → 2.6.7
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/CHANGELOG.md +37 -0
- package/README.md +40 -177
- package/manifests/documentation.yaml +68 -11
- package/manifests/instructionRules.yaml +376 -0
- package/manifests/platform.full.yaml +15 -0
- package/package.json +3 -1
- package/scripts/docs/render-instructions.mjs +42 -0
- package/src/core/projectImportant.js +1 -1
- package/src/core/uninstall.js +1 -1
- package/src/render/instructionRenderer.js +226 -0
- package/templates/.gitignore +2 -2
- package/templates/.omp/RULES.md +1 -0
- package/templates/AGENTS.md +71 -147
- package/templates/CLAUDE.md +67 -141
- package/templates/docs/UKIT_INTERNALS.md +219 -0
- package/templates/instructions/core.md +210 -0
- package/templates/instructions/layout.yaml +149 -0
- package/templates/instructions/overlays/agents.md +15 -0
- package/templates/instructions/overlays/claude.md +3 -0
- package/templates/instructions/overlays/omp-rules.md +74 -0
- package/templates/instructions/overlays/repo.md +9 -0
- package/templates/instructions/repo-vars.yaml +23 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import YAML from 'yaml';
|
|
4
|
+
|
|
5
|
+
import { renderTemplateString } from './renderTemplate.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Deterministic core/overlay renderer for the five instruction-contract
|
|
9
|
+
* outputs (SPEC §8.1, FR-004..FR-007). Pure functions; disk access only in
|
|
10
|
+
* renderInstructionsFromDisk / checkRenderedInstructions.
|
|
11
|
+
*
|
|
12
|
+
* Emission rules (byte-exact):
|
|
13
|
+
* h1 + '\n' + banner + '\n\n' + blocks.join('') [+ trailer] [+ '\n']
|
|
14
|
+
* where each `## ` block is `## <heading>` + the raw body slice — the body
|
|
15
|
+
* keeps its leading AND trailing whitespace, so concatenation reproduces
|
|
16
|
+
* the source bytes exactly; the reserved
|
|
17
|
+
* `__preamble__` block is the source's leading non-## text verbatim.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export const PREAMBLE_KEY = '__preamble__';
|
|
21
|
+
|
|
22
|
+
const SECTION_HEADING_RE = /^## (.+)$/gm;
|
|
23
|
+
const LEFTOVER_TOKEN_RE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Split markdown into a Map<heading, body> at `## ` boundaries.
|
|
27
|
+
* `### ` and deeper stay inside the enclosing body. Leading non-## text lands
|
|
28
|
+
* under the reserved `__preamble__` key (absent when the file starts with `## `).
|
|
29
|
+
* Throws on a duplicate `## ` heading, naming it.
|
|
30
|
+
*/
|
|
31
|
+
export function parseSections(markdown) {
|
|
32
|
+
const sections = new Map();
|
|
33
|
+
const matches = [...markdown.matchAll(SECTION_HEADING_RE)];
|
|
34
|
+
const first = matches[0];
|
|
35
|
+
|
|
36
|
+
const preamble = first ? markdown.slice(0, first.index) : markdown;
|
|
37
|
+
if (preamble.trim().length > 0) {
|
|
38
|
+
// Raw slice kept verbatim: its trailing newline(s) are the separator
|
|
39
|
+
// before the first ## section (e.g. the RULES.md preamble block).
|
|
40
|
+
sections.set(PREAMBLE_KEY, preamble);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < matches.length; i++) {
|
|
44
|
+
const m = matches[i];
|
|
45
|
+
const heading = m[1].trimEnd();
|
|
46
|
+
if (sections.has(heading)) {
|
|
47
|
+
throw new Error(`duplicate ## heading in source: ${heading}`);
|
|
48
|
+
}
|
|
49
|
+
const bodyStart = m.index + m[0].length;
|
|
50
|
+
const bodyEnd = i + 1 < matches.length ? matches[i + 1].index : markdown.length;
|
|
51
|
+
// Raw slice up to (not including) the next `## ` heading — leading AND
|
|
52
|
+
// trailing whitespace are byte-meaningful (marker line vs blank line
|
|
53
|
+
// under the heading; multi-blank separators between sections).
|
|
54
|
+
sections.set(heading, markdown.slice(bodyStart, bodyEnd));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return sections;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Render one layout output. `outputSpec` = layout.outputs[i];
|
|
62
|
+
* `sources` = { <sourceName>: Map<heading, body> }; `vars` = flat/nested
|
|
63
|
+
* variables applied when `outputSpec.resolve_vars` is true.
|
|
64
|
+
* Reserved specifiers: heading '__preamble__' emits the source's leading
|
|
65
|
+
* non-## block verbatim (no `## ` prefix); heading '*' expands to every
|
|
66
|
+
* `## ` section of that source in file order.
|
|
67
|
+
*/
|
|
68
|
+
export function renderOutput(outputSpec, sources, vars = {}) {
|
|
69
|
+
if (!Array.isArray(outputSpec.sections) || outputSpec.sections.length === 0) {
|
|
70
|
+
throw new Error(`output ${outputSpec.target}: sections must be a non-empty list`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const blocks = [];
|
|
74
|
+
for (const ref of outputSpec.sections) {
|
|
75
|
+
const source = sources.get(ref.from);
|
|
76
|
+
if (!source) {
|
|
77
|
+
throw new Error(`output ${outputSpec.target}: unknown source '${ref.from}'`);
|
|
78
|
+
}
|
|
79
|
+
if (ref.heading === '*') {
|
|
80
|
+
for (const [heading, body] of source) {
|
|
81
|
+
if (heading === PREAMBLE_KEY) continue;
|
|
82
|
+
blocks.push(`## ${heading}${body}`);
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (ref.heading === PREAMBLE_KEY) {
|
|
87
|
+
if (!source.has(PREAMBLE_KEY)) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`output ${outputSpec.target}: source '${ref.from}' has no __preamble__ block`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
blocks.push(source.get(PREAMBLE_KEY));
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!source.has(ref.heading)) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`output ${outputSpec.target}: heading '${ref.heading}' missing from source '${ref.from}'`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
blocks.push(`## ${ref.heading}${source.get(ref.heading)}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let out = `${outputSpec.h1}\n${outputSpec.banner}\n\n${blocks.join('')}`;
|
|
104
|
+
if (outputSpec.trailer !== undefined && outputSpec.trailer !== null && outputSpec.trailer !== '') {
|
|
105
|
+
if (!out.endsWith('\n')) out += '\n';
|
|
106
|
+
out += `${outputSpec.trailer}`;
|
|
107
|
+
}
|
|
108
|
+
if (!out.endsWith('\n')) out += '\n';
|
|
109
|
+
|
|
110
|
+
if (outputSpec.resolve_vars) {
|
|
111
|
+
out = renderTemplateString(out, vars);
|
|
112
|
+
const leftover = out.match(LEFTOVER_TOKEN_RE);
|
|
113
|
+
if (leftover) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`output ${outputSpec.target}: unresolved template variable '{{${leftover[1]}}}'`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Render every output in `layout` from `sourceTexts` ({name: markdown}).
|
|
124
|
+
* Source names are looked up as `sources.get(ref.from)`; overlay sources are
|
|
125
|
+
* named `overlay:<file-stem>` by convention in renderInstructionsFromDisk.
|
|
126
|
+
* `varsByResolve` supplies variables to outputs with resolve_vars: true.
|
|
127
|
+
* FR-006 strictness: a `## ` section in any source referenced by zero outputs
|
|
128
|
+
* throws, naming the heading.
|
|
129
|
+
*/
|
|
130
|
+
export function renderAll(layout, sourceTexts, varsByResolve = {}) {
|
|
131
|
+
if (!layout || !Array.isArray(layout.outputs) || layout.outputs.length === 0) {
|
|
132
|
+
throw new Error('layout.outputs must be a non-empty list');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const sources = new Map(
|
|
136
|
+
Object.entries(sourceTexts).map(([name, text]) => [name, parseSections(text)]),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Referenced-heading accounting for the unreferenced-section check.
|
|
140
|
+
const referenced = new Map(); // sourceName -> Set<heading>
|
|
141
|
+
const mark = (from, heading) => {
|
|
142
|
+
if (!referenced.has(from)) referenced.set(from, new Set());
|
|
143
|
+
referenced.get(from).add(heading);
|
|
144
|
+
};
|
|
145
|
+
for (const spec of layout.outputs) {
|
|
146
|
+
for (const ref of spec.sections || []) {
|
|
147
|
+
if (ref.heading === '*') {
|
|
148
|
+
const source = sources.get(ref.from);
|
|
149
|
+
if (!source) throw new Error(`unknown source '${ref.from}' referenced by '*'`);
|
|
150
|
+
for (const heading of source.keys()) {
|
|
151
|
+
if (heading !== PREAMBLE_KEY) mark(ref.from, heading);
|
|
152
|
+
}
|
|
153
|
+
} else if (ref.heading !== PREAMBLE_KEY) {
|
|
154
|
+
mark(ref.from, ref.heading);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const [name, source] of sources) {
|
|
159
|
+
for (const heading of source.keys()) {
|
|
160
|
+
if (heading === PREAMBLE_KEY) continue;
|
|
161
|
+
if (!referenced.get(name) || !referenced.get(name).has(heading)) {
|
|
162
|
+
throw new Error(`source '${name}': ## section '${heading}' referenced by zero outputs`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const rendered = new Map();
|
|
168
|
+
for (const spec of layout.outputs) {
|
|
169
|
+
const vars = spec.resolve_vars ? varsByResolve : {};
|
|
170
|
+
rendered.set(spec.target, renderOutput(spec, sources, vars));
|
|
171
|
+
}
|
|
172
|
+
return rendered;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const SOURCE_FILES = {
|
|
176
|
+
core: 'templates/instructions/core.md',
|
|
177
|
+
'overlay:claude': 'templates/instructions/overlays/claude.md',
|
|
178
|
+
'overlay:agents': 'templates/instructions/overlays/agents.md',
|
|
179
|
+
'overlay:omp-rules': 'templates/instructions/overlays/omp-rules.md',
|
|
180
|
+
'overlay:repo': 'templates/instructions/overlays/repo.md',
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Read layout + sources + repo-vars + package.json version from disk, render
|
|
185
|
+
* all outputs, and (write=true) write each target. Returns Map<target, content>.
|
|
186
|
+
*/
|
|
187
|
+
export async function renderInstructionsFromDisk({ repoRoot, write = true } = {}) {
|
|
188
|
+
const abs = (p) => path.join(repoRoot, p);
|
|
189
|
+
const layout = YAML.parse(
|
|
190
|
+
fs.readFileSync(abs('templates/instructions/layout.yaml'), 'utf8'),
|
|
191
|
+
);
|
|
192
|
+
const repoVars =
|
|
193
|
+
YAML.parse(fs.readFileSync(abs('templates/instructions/repo-vars.yaml'), 'utf8')) || {};
|
|
194
|
+
const pkg = JSON.parse(fs.readFileSync(abs('package.json'), 'utf8'));
|
|
195
|
+
|
|
196
|
+
const sourceTexts = {};
|
|
197
|
+
for (const [name, rel] of Object.entries(SOURCE_FILES)) {
|
|
198
|
+
sourceTexts[name] = fs.readFileSync(abs(rel), 'utf8');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const vars = { ...repoVars, ukit: { ...(repoVars.ukit || {}), version: pkg.version } };
|
|
202
|
+
const rendered = renderAll(layout, sourceTexts, vars);
|
|
203
|
+
|
|
204
|
+
if (write) {
|
|
205
|
+
for (const [target, content] of rendered) {
|
|
206
|
+
fs.writeFileSync(abs(target), content);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return rendered;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* → string[] of drifted target paths (empty = clean). Drift means rendered
|
|
214
|
+
* bytes differ from disk bytes, or the target is missing.
|
|
215
|
+
*/
|
|
216
|
+
export async function checkRenderedInstructions({ repoRoot } = {}) {
|
|
217
|
+
const rendered = await renderInstructionsFromDisk({ repoRoot, write: false });
|
|
218
|
+
const drift = [];
|
|
219
|
+
for (const [target, content] of rendered) {
|
|
220
|
+
const file = path.join(repoRoot, target);
|
|
221
|
+
if (!fs.existsSync(file) || fs.readFileSync(file, 'utf8') !== content) {
|
|
222
|
+
drift.push(target);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return drift;
|
|
226
|
+
}
|
package/templates/.gitignore
CHANGED
|
@@ -54,8 +54,8 @@ Thumbs.db
|
|
|
54
54
|
# templates/.claude/ukit/index/task-budget-validator.mjs). The root .gitignore
|
|
55
55
|
# already carries the anchored /.claude/ /.codex/ /.omp/ /.ukit/ exclusions.
|
|
56
56
|
opencode.json
|
|
57
|
-
AGENTS.md
|
|
58
|
-
CLAUDE.md
|
|
57
|
+
/AGENTS.md
|
|
58
|
+
/CLAUDE.md
|
|
59
59
|
docs/STATUS.md
|
|
60
60
|
docs/TASKS.md
|
|
61
61
|
.codex/settings.local.json
|
package/templates/.omp/RULES.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# .omp/RULES.md — sticky always-apply rules
|
|
2
|
+
<!-- generated: templates/instructions/ — edit sources, then yarn docs:render -->
|
|
2
3
|
|
|
3
4
|
omp re-attaches this file near every turn from its native location (`.omp/RULES.md` only, never a
|
|
4
5
|
copy elsewhere). It carries the always-apply subset of root `AGENTS.md` that must survive even when
|
package/templates/AGENTS.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# AGENTS.md — {{project.name}}
|
|
2
|
+
<!-- generated: templates/instructions/ — edit sources, then yarn docs:render -->
|
|
2
3
|
|
|
3
4
|
## Core Rule
|
|
5
|
+
<!-- RULES: CORE-01 CORE-02 -->
|
|
4
6
|
|
|
5
7
|
- Human-facing UKit workflow should collapse to one remembered command: `ukit install`.
|
|
6
8
|
- After install, default to natural-language work inside **Claude Code / Codex / OpenCode / omp**.
|
|
@@ -9,48 +11,49 @@
|
|
|
9
11
|
|
|
10
12
|
## Fast Classification
|
|
11
13
|
|
|
14
|
+
<!-- RULE: CLS-01 -->
|
|
12
15
|
- **Trivial** — typo, label, small rename, spacing, toggle flag, obvious config change.
|
|
13
16
|
- Act directly. No doc reads. No planning. No index. No agents.
|
|
17
|
+
<!-- RULE: CLS-02 -->
|
|
14
18
|
- **Simple** — 1-2 files, clear scope, existing pattern.
|
|
15
19
|
- Handle directly. Pull only the smallest useful context via resolver or targeted read.
|
|
20
|
+
<!-- RULE: CLS-03 -->
|
|
16
21
|
- **Non-trivial / Risky** — auth, security, migration, uninstall, shared runtime, race/flaky, data-loss.
|
|
17
22
|
- Read deeper, verify harder, and avoid shortcuts.
|
|
18
23
|
- Use index-first loop, then skill activation, then targeted verification.
|
|
19
24
|
|
|
20
25
|
## Execution Contract (mandatory)
|
|
21
26
|
|
|
27
|
+
<!-- RULE: EXEC-01 -->
|
|
22
28
|
- For explicit implement/apply/fix requests, **continue until the actual edit is made** or a real blocker is found.
|
|
23
29
|
- Do NOT stop after a read-only inspection step (Read/Grep/Glob/search).
|
|
24
|
-
|
|
30
|
+
<!-- RULE: EXEC-03 -->
|
|
31
|
+
- If routed state says `pull-indexed-context`, treat it as an internal continuation step, not a stopping point — after the bounded read, **continue to edit/verify in the same turn** when safe.
|
|
25
32
|
- If routed state shows `continuation required` or a stuck-lane rescue mode, finish the named milestone before widening reads or repeating analysis.
|
|
33
|
+
<!-- RULE: EXEC-02 -->
|
|
26
34
|
- **Do NOT say "done", "applied", or "fixed" after Read/Grep/analysis alone.** Completion wording requires concrete Edit/Write evidence in the current turn, and verification when the scope is risky.
|
|
35
|
+
<!-- RULE: EXEC-04 -->
|
|
27
36
|
- **Every stop says why — no silent idle.** When a turn ends because only the user can act (login, approval, protected-file edit), open the reply with one line naming the exact action: `WAITING ON YOU: <command/action>`, and schedule a one-shot wakeup (~20-30 min) when the harness provides one so the session re-checks and auto-continues once the user has acted. An ended turn cannot observe external/auth changes by itself, so without that line (and the wakeup) the idle session looks identical to a stall. Any error — failed command, hook, test, publish — is reported verbatim in the same turn, never silently retried past the user.
|
|
28
37
|
|
|
29
38
|
## Long-Run Continuity
|
|
39
|
+
<!-- RULES: LONG-01 LONG-02 -->
|
|
30
40
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
- After any compact or handoff: do not reread pre-compact context — continue from the persisted disk state, delegate broad work, keep replies short. If the first turn after a compact still sits at ≥60% of the cap, stop rereading immediately and recover by delegating or starting a fresh session from the disk state; do not burn the window again.
|
|
35
|
-
- A run ends only on completion evidence, a genuine blocker, or a user-only action — and per the Execution Contract above, every such stop names its reason in the final reply.
|
|
41
|
+
- Near token-cap: **LAND one thing** end-to-end (edit + verify, ≤3 tool calls), **DEFER** the rest into `docs/STATUS.md` or bounded `docs/AI_HANDOFF/` tasks, **DELEGATE** broad work to subagents. Only then compact.
|
|
42
|
+
- After any compact or handoff: continue from the persisted disk state — never reread pre-compact context; delegate broad work, keep replies short.
|
|
43
|
+
- A run ends only on completion evidence, a genuine blocker, or a user-only action — every such stop names its reason (Execution Contract). Detail: `docs/UKIT_INTERNALS.md`.
|
|
36
44
|
|
|
37
45
|
## Index-First Loop
|
|
38
46
|
|
|
39
47
|
For any task that needs code context:
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
3.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
`query-index` and `resolve-context` print an `outline:` block (`line: signature`) for the
|
|
50
|
-
top suspects. Use it to jump straight to the relevant region with
|
|
51
|
-
`Read(file, offset=<line>)` instead of reading the whole file.
|
|
52
|
-
The outline locates code; it does not describe behaviour. **Any code you are about to
|
|
53
|
-
change must still be read.**
|
|
49
|
+
<!-- RULE: IDX-01 -->
|
|
50
|
+
1. Check if index is fresh (`.cache/index/` artifacts). If stale or missing, refresh (`node .claude/ukit/index/refresh-index.mjs`).
|
|
51
|
+
2. Query likely files: `node .claude/ukit/index/query-index.mjs "<error|symbol|path>"`.
|
|
52
|
+
3. For bug signatures: `node .claude/ukit/index/triage.mjs "<error signature>"`.
|
|
53
|
+
<!-- RULE: IDX-02 -->
|
|
54
|
+
4. Open only the **top 1-3 suspect files first**, then widen if needed. `query-index`/`resolve-context` print an `outline:` block — jump straight to `Read(file, offset=<line>)`.
|
|
55
|
+
<!-- RULE: IDX-03 -->
|
|
56
|
+
The outline locates code; it does not describe behaviour. **Any code you are about to change must still be read.**
|
|
54
57
|
5. For analog/reuse patterns, check if `resolve-context` returns related existing patterns.
|
|
55
58
|
|
|
56
59
|
For clearly non-code specialist lanes (docs-only, status, task queue), skip the source-code index.
|
|
@@ -58,14 +61,12 @@ For clearly non-code specialist lanes (docs-only, status, task queue), skip the
|
|
|
58
61
|
## Automatic Skill Activation (mandatory)
|
|
59
62
|
|
|
60
63
|
- End users should not need to know skill names.
|
|
64
|
+
<!-- RULE: SKILL-01 -->
|
|
61
65
|
- On every non-trivial task — and again after the first relevant tool calls — inspect installed project-local skills and **auto-activate the matching skill immediately**.
|
|
62
66
|
- Match from both prompt wording and tool/file evidence.
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
- Prefer routed context and routed verification over ad-hoc broad reading.
|
|
67
|
-
- Reuse `.claude/ukit/skill-router-state.json` when it already carries compact route memory.
|
|
68
|
-
- If shared route state already includes `previous-context` or `recent-output`, reuse those first.
|
|
67
|
+
<!-- RULES: SKILL-02 SKILL-03 -->
|
|
68
|
+
- Use the smallest effective set, usually 1-2 skills; if evidence becomes more specific than the original prompt, upgrade the active skill choice immediately.
|
|
69
|
+
- Prefer routed context and routed verification over ad-hoc broad reading; reuse `.claude/ukit/skill-router-state.json` when it already carries compact route memory.
|
|
69
70
|
|
|
70
71
|
### Common skill triggers
|
|
71
72
|
|
|
@@ -80,12 +81,11 @@ For clearly non-code specialist lanes (docs-only, status, task queue), skip the
|
|
|
80
81
|
|
|
81
82
|
## Internal Helper Policy
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
- Prefer `node .claude/ukit/index/resolve-context.mjs
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
- If the workspace needs a refresh, prefer telling them to rerun `ukit install`.
|
|
84
|
+
<!-- RULES: HELP-01 HELP-02 -->
|
|
85
|
+
- Prefer the internal index helpers (`node .claude/ukit/index/route-task.mjs`, `resolve-context.mjs`, `verify-context.mjs`) for routing, related-file context, and verification lanes.
|
|
86
|
+
- **Do not ask normal contributors to run internal helper commands** or memorize maintainer commands (`ukit doctor`, `ukit diff`, `ukit uninstall`) — run them yourself.
|
|
87
|
+
<!-- RULE: FALLBACK-01 -->
|
|
88
|
+
- If the workspace needs a refresh or runtime files are missing/corrupt, tell maintainers to rerun `ukit install`. Detail: `docs/UKIT_INTERNALS.md`.
|
|
89
89
|
|
|
90
90
|
## Skill Quality (maintainer-only)
|
|
91
91
|
|
|
@@ -93,150 +93,90 @@ For clearly non-code specialist lanes (docs-only, status, task queue), skip the
|
|
|
93
93
|
|
|
94
94
|
## UKit v{{ukit.version}} Shared Runtime
|
|
95
95
|
|
|
96
|
-
- Shared runtime state lives in `.ukit/storage
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
- Reusable cache/compact/output state lives in `.ukit/storage/cache/prompt-cache.json`, `.ukit/storage/cache/compact-history.json`, `.ukit/storage/cache/compact-pressure.json`, `.ukit/storage/cache/output-history.json`, and preserved raw tool outputs under `.ukit/storage/cache/tee/`.
|
|
101
|
-
- Shared route memory lives in `.claude/ukit/skill-router-state.json`.
|
|
102
|
-
- If shared route state already includes compact `previous-context` or `recent-output`, reuse those first.
|
|
103
|
-
- If an older repo still has a visible `ukit/` runtime root, rerun `ukit install`; UKit should migrate the shared runtime into hidden `.ukit/` when safe.
|
|
104
|
-
- Maintainers can inspect runtime state with `ukit status` and `ukit memory export`, but normal teammates should still only need `ukit install`.
|
|
105
|
-
- If runtime files are missing or corrupt, tell maintainers to rerun `ukit install`.
|
|
106
|
-
- Threshold-based compact pressure is internal orchestration; do not expose it to users.
|
|
107
|
-
- For Codex Desktop long sessions, UKit can use soft auto-compact handoffs. Default `compact.codexContext.compactTarget=150` means about 150 compact handoff lines (120-150 preferred, hard max 170), not 150 tokens.
|
|
96
|
+
- Shared runtime state lives in `.ukit/storage/`; `.ukit/storage/config.json` is the source of runtime toggles (compact, token pipeline, router, memory, validation, Safe Patch).
|
|
97
|
+
- Reuse `.ukit/storage/memory/` and `ukit memory recall "<current task>"` before asking users to restate decisions or widening doc reads; maintainers can inspect state with `ukit status` / `ukit memory export`.
|
|
98
|
+
- Shared route memory lives in `.claude/ukit/skill-router-state.json`; reuse compact `previous-context`/`recent-output` first.
|
|
99
|
+
- If runtime files are missing/corrupt or an old visible `ukit/` root remains, rerun `ukit install`. Cache state (`.ukit/storage/cache/output-history.json`, tee/) + Codex handoff detail: `docs/UKIT_INTERNALS.md`.
|
|
108
100
|
|
|
109
101
|
## Prompt Caching
|
|
102
|
+
<!-- RULES: CTX-01 CTX-02 CTX-03 CTX-04 CTX-05 CTX-06 CTX-07 CTX-08 CTX-09 CTX-10 -->
|
|
110
103
|
|
|
111
|
-
- Deterministic, stable context lets a provider reuse a prompt prefix — and it is worth doing even when no caching is guaranteed.
|
|
112
104
|
- Full ruleset: `docs/PROMPT_CACHING.md` (read on demand; it is not loaded into every session).
|
|
113
105
|
- CTX-01 deterministic segment bytes · CTX-02 keep roles and order · CTX-03 keep tool IDs and continuation state · CTX-04 no clock/random IDs in static blocks · CTX-05 compaction starts a new epoch · CTX-06 never change data to match a cache · CTX-07 no unconfirmed cache fields · CTX-08 tool-result reuse needs valid freshness · CTX-09 missing usage is unknown, not zero · CTX-10 never cut a required check to reduce calls.
|
|
114
|
-
- Never sort messages, trim meaningful whitespace, rewrite reasoning fields, or move a user request into system context.
|
|
115
|
-
- Upstream vendor docs are reference only — never a guarantee about the route you actually use.
|
|
116
106
|
|
|
117
107
|
## Safe Patch Protocol
|
|
108
|
+
<!-- RULES: SAFE-02 SAFE-03 SAFE-01 -->
|
|
118
109
|
|
|
119
|
-
-
|
|
120
|
-
- For risky/shared/large edits, prefer unique current-file anchors over line numbers or stale pasted blocks.
|
|
121
|
-
- Do not silently merge stale specs: if `old_string` is missing or ambiguous, re-read current source and ask whether to apply as-is, adapt, or skip.
|
|
110
|
+
- For risky/shared/large edits, prefer unique current-file anchors over line numbers or stale pasted blocks; if `old_string` is missing or ambiguous, re-read current source and ask whether to apply as-is, adapt, or skip.
|
|
122
111
|
- Preserve UTF-8 BOM/no-BOM and LF/CRLF for existing multilingual/user-authored files.
|
|
123
|
-
-
|
|
112
|
+
- Internal helper + detail: `docs/UKIT_INTERNALS.md` (`node .claude/ukit/index/safe-patch.mjs`).
|
|
124
113
|
|
|
125
114
|
## Handoff Quality Gate — OPT-IN
|
|
115
|
+
<!-- RULE: HAND-01 -->
|
|
126
116
|
|
|
127
117
|
CHỈ kích hoạt khi task đi qua `docs/AI_HANDOFF/` (user nói "execute task TASK-xxx" hoặc target là `docs/AI_HANDOFF/tasks/*.md`). Daily prompt → KHÔNG đụng, flow cũ giữ nguyên.
|
|
128
118
|
|
|
129
119
|
Khi Handoff mode: đọc `docs/AI_HANDOFF/RULES.md` để biết 4 phase (Idea+Plan → Create Tasks → Implement+Test → Review+Test) + state machine + comment thread + self-report model. Config: `.ukit/storage/config.json` → `handoff.*`.
|
|
130
120
|
|
|
131
121
|
## Context + Verification Budget
|
|
122
|
+
<!-- RULE: BUDGET-01 -->
|
|
132
123
|
|
|
133
124
|
- **Trivial**: no docs, and no index query unless the file target is unclear.
|
|
134
125
|
- **Simple**: `docs/MEMORY.md` only, plus resolver-selected files/tests.
|
|
135
126
|
- **Non-trivial**: `docs/MEMORY.md` + `docs/PROJECT.md` + `docs/CODE_MAP.md`.
|
|
136
|
-
|
|
137
|
-
- `docs/
|
|
138
|
-
- `docs/WORKLOG.md`: only recent relevant entries. Follow the Budget Rules at the top of the file; archive oldest entries to `docs/WORKLOG_ARCHIVE.md` when over limits.
|
|
127
|
+
<!-- RULES: BUDGET-02 BUDGET-03 -->
|
|
128
|
+
- `docs/STATUS.md` for open-ended/continue prompts; `docs/TASKS.md` only for queued-task prompts; `docs/WORKLOG.md` recent entries only (archive overflow).
|
|
139
129
|
- Follow routed verification policy: targeted first, widen only when risk/shared scope justifies it, ask before blanket broad runs.
|
|
140
130
|
|
|
141
131
|
## Living Status Workflow
|
|
132
|
+
<!-- RULE: STATUS-01 -->
|
|
142
133
|
|
|
143
|
-
- `docs/STATUS.md` captures compact current state
|
|
144
|
-
-
|
|
145
|
-
- For "what next?" / "continue" prompts without a concrete target, use `next-step` and show a freshness cue before relying on the status file.
|
|
146
|
-
- For concrete debug/implementation/review prompts, keep the concrete workflow primary even if the user asks for an approach or next step.
|
|
147
|
-
- After meaningful work, use `update-status`; skip trivial/no-state-change tasks and avoid transcript-style noise.
|
|
148
|
-
- `docs/TASKS.md` is a local AI task queue: prefer `Ready for AI` when asked to pick queued work, and clean duplicates/prune `Done Recently` safely when reading/updating it.
|
|
134
|
+
- `docs/STATUS.md` captures compact current state; it is not source truth and must not replace source/index-first investigation.
|
|
135
|
+
- For "what next?" / "continue" prompts, use `next-step` with a freshness cue; after meaningful work use `update-status`. `docs/TASKS.md` is the local AI task queue — prefer `Ready for AI`. Detail: `docs/UKIT_INTERNALS.md`.
|
|
149
136
|
|
|
150
137
|
## Small-Task Maintainer (internal)
|
|
138
|
+
<!-- RULE: SUBAG-02 -->
|
|
151
139
|
|
|
152
|
-
-
|
|
153
|
-
- Use it for safe/reversible UKit chores: dọn `docs/TASKS.md`, queued-task classification, fast-vs-slow/safe-vs-risky lane decisions, skill-routing/step-budget hints, agent context-budget decisions, compact/summary decisions, docs/status summarization, auto-triage, queue maintenance, and small workspace cleanup.
|
|
154
|
-
- Run it as a sidecar/parallel lane only; do not block, replace, or slow the user task.
|
|
155
|
-
- If the small-task lane sees security, risky/shared code, release/publish, data-loss, architecture, deep-reasoning risk, weak context, or quality risk, it hands back to the main model.
|
|
156
|
-
- This is optional internal orchestration config from `.ukit/storage/config.json`; never turn it into an end-user workflow.
|
|
157
|
-
- Always preserve the CoDev priority: quality > safety > speed > token discipline.
|
|
140
|
+
- The `ukit-small-task-maintainer` subagent (`subagents.smallTaskModel`, default `unic-lite`) handles safe/reversible UKit chores as a sidecar lane — never block or slow the user task; risky work hands back to the main model. Detail: `docs/UKIT_INTERNALS.md`.
|
|
158
141
|
|
|
159
142
|
## Post-Edit Sidecar Review (internal)
|
|
160
143
|
|
|
161
|
-
-
|
|
162
|
-
- Only launch it once write evidence AND verification evidence already exist for the task (never before; never as a substitute for either).
|
|
163
|
-
- Launch the `code-reviewer` agent (see the harness table under 3-Tier Model Routing) with `REVIEW_TARGET_TYPE=diff`, in the background, on the `smart` tier per `subagents.diffReviewModel`. Do not wait for it — continue and report the task as done using the normal completion rules.
|
|
164
|
-
- Its findings are advisory only: never re-open, block, or delay the already-reported completion on their account. Surface them to the user as a follow-up note if/when they arrive.
|
|
165
|
-
- This is internal orchestration — end users never invoke it directly; `ukit install` plus natural language remains the whole surface. No new commands.
|
|
144
|
+
- When routed state's `routeSummary.line` carries `review=code-reviewer(diff)`, launch the `code-reviewer` agent in the background (`smart` tier) **only after** write + verification evidence exists; findings are advisory — never block the already-reported completion. Detail: `docs/UKIT_INTERNALS.md`.
|
|
166
145
|
|
|
167
146
|
## Selective Subagent Policy (internal only)
|
|
147
|
+
<!-- RULE: SUBAG-01 -->
|
|
168
148
|
|
|
169
|
-
- Keep direct execution as the default for trivial/simple work.
|
|
170
|
-
- Delegate only when it meaningfully shrinks context or enables useful parallel progress.
|
|
171
|
-
- Good delegation triggers:
|
|
172
|
-
- noisy side lanes (broad logs/search/test output)
|
|
173
|
-
- 3+ independent failures/files/checks
|
|
174
|
-
- explicit batch/plan execution
|
|
175
|
-
- broad implementation/debug lanes that can return a concise summary
|
|
176
|
-
- If route memory includes `delegate=<lane>`, treat it as an internal hint after any required indexed-context step.
|
|
149
|
+
- Keep direct execution as the default for trivial/simple work; delegate only when it meaningfully shrinks context or enables useful parallel progress (noisy side lanes, 3+ independent failures, batch plans, broad debug lanes).
|
|
177
150
|
- Do not ask end users to name agents or remember agent commands.
|
|
178
151
|
|
|
179
152
|
## Adaptive Autonomy
|
|
153
|
+
<!-- RULE: AUTO-01 -->
|
|
180
154
|
|
|
181
155
|
- `autonomy.level` in `.ukit/storage/config.json` controls how much UKit acts without asking first: `conservative` (ask more), `balanced` (default), `free-run` (auto-run more), `vibecode` (run one prompt to a finished result; the completion gate stops only on completion evidence, a genuine blocker, or a dangerous-command decision).
|
|
182
156
|
- End users should not need to change this; maintainers may tune it per-project.
|
|
183
157
|
|
|
184
158
|
## 3-Tier Model Routing
|
|
159
|
+
<!-- RULES: TIER-01 TIER-02 -->
|
|
185
160
|
|
|
186
161
|
**Internal orchestration only — end users still just use natural language. No new commands.**
|
|
187
162
|
|
|
188
|
-
UKit routes tasks to one of three model tiers based on task complexity:
|
|
189
|
-
|
|
190
163
|
| Tier | Generic alias | Claude model | Typical tasks |
|
|
191
164
|
|------|--------------|--------------|---------------|
|
|
192
165
|
| lite | `unic-lite` | claude-haiku | Reads, git queries, bash summaries, small doc edits |
|
|
193
166
|
| code | `unic-code` | claude-sonnet | Normal coding, local fixes, shared edits, builds, debugging, impact mapping |
|
|
194
167
|
| smart | `unic-smart` | claude-opus | Release review/audit, and escalated deep reasoning after repeated failure |
|
|
195
168
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
| `tiny-fix` | lite |
|
|
201
|
-
| `local-fix`, `local-build`, `shared-edit`, `find-cause`, `map-impact` | code |
|
|
202
|
-
| `review-release` | smart |
|
|
203
|
-
|
|
204
|
-
### How a tier is actually bound
|
|
205
|
-
|
|
206
|
-
The main session model never changes mid-turn. A tier only takes effect when work is handed to
|
|
207
|
-
an agent whose own definition binds that model:
|
|
208
|
-
|
|
209
|
-
| Harness | Agent definitions | How to launch one |
|
|
210
|
-
|---------|-------------------|-------------------|
|
|
211
|
-
| Claude Code | `.claude/agents/*.md` (`model:` frontmatter) | Agent tool, `subagent_type: "<name>"` |
|
|
212
|
-
| omp | `.omp/agents/*.md` (`model: "@lite"` / `"@code"` / `"@smart"` / `"@vision"`, resolved through `modelRoles` in `.omp/config.yml`) | task-agent `<name>` |
|
|
213
|
-
|
|
214
|
-
When a task's contract maps to a tier other than the current session model, hand it to the
|
|
215
|
-
matching agent instead of doing it inline. Doing everything inline is exactly what makes UKit
|
|
216
|
-
behave as if it only had one model — the tier table above has no effect on its own.
|
|
217
|
-
|
|
218
|
-
### Escalation rule
|
|
219
|
-
|
|
220
|
-
When the same file or symbol fails `debugLoopThreshold` (default: 2) times in one session, UKit routes the next attempt one tier higher (capped at `smart`). Config: `orchestration.escalation` in `.ukit/storage/config.json`.
|
|
221
|
-
|
|
222
|
-
This is internal orchestration — end users do not need to know about tiers, thresholds, or escalation. The AI handles routing transparently.
|
|
223
|
-
|
|
224
|
-
### Vision lane (capability, not a cost tier)
|
|
225
|
-
|
|
226
|
-
`unic-vision` is a **capability lane**, not a fourth cost tier — it is orthogonal to lite/code/smart above and never appears as a row in the tier table. Whether a mapping can read images is a capability fact, not a provider fact: a mapping that has not **verified** native vision must never guess at image contents — choose native-first when verified, otherwise route to the specialist.
|
|
227
|
-
|
|
228
|
-
- **Gateway detection**: UNIC routing for a Claude Code session is decided ONLY by what changes Claude Code's own outbound endpoint — `ANTHROPIC_BASE_URL` (env var, or the `env.ANTHROPIC_BASE_URL` key in project/home `.claude/settings.json`) containing `unicjsc.com`. Other tools' configs — Codex `config.toml`, Kilo `secrets.json`, or an `OPENAI_BASE_URL` env var — describe a different tool's endpoint entirely and never decide this session's routing.
|
|
229
|
-
- **Advisory routing (no hard block)**: when an image reaches the prompt, the vision router reminds the session to have `ukit-vision-analyst` analyse it before relying on its contents. Edits are **never blocked** — correctness relies on the model routing images to the analyst instead of guessing.
|
|
230
|
-
- This is internal orchestration — end users never invoke a vision command directly; `ukit install` plus natural language remains the whole surface. No new commands.
|
|
169
|
+
- The main session model never changes mid-turn: a tier takes effect only when work is handed to an agent whose own definition binds that model (`model:` frontmatter in `.claude/agents/*.md`; `model:` `@lite`/`@code`/`@smart`/`@vision` in `.omp/agents/*.md` resolved via `modelRoles`).
|
|
170
|
+
- Contract map: `tiny-fix` → lite · `local-fix`, `local-build`, `shared-edit`, `find-cause`, `map-impact` → code · `review-release` → smart.
|
|
171
|
+
- Escalation: same file/symbol failing `debugLoopThreshold` (default 2) times in one session routes the next attempt one tier higher, capped at `smart`.
|
|
172
|
+
- `unic-vision` is a capability lane, not a cost tier — unverified vision must never guess at image contents; route images to `ukit-vision-analyst`. Full harness table + gateway detection detail: `docs/UKIT_INTERNALS.md`.
|
|
231
173
|
|
|
232
174
|
## Session Start — OpenCode
|
|
233
175
|
|
|
176
|
+
<!-- RULE: HOST-OC-01 -->
|
|
234
177
|
At the start of every OpenCode session, before working on the first task:
|
|
235
|
-
1.
|
|
236
|
-
2.
|
|
237
|
-
3. For every non-trivial task: run `/ukit-route <task summary>` immediately to get skill + context hints **before** writing any code.
|
|
238
|
-
4. If the route result points to a skill, read that SKILL.md before acting — do not skip this step.
|
|
239
|
-
5. If `.ukit/storage/config.json` has `router.enabled: true`, prefer the router output over ad-hoc guessing.
|
|
178
|
+
1. Reuse matching route hints from `.claude/ukit/skill-router-state.json`; scan `.claude/skills/` (listing only) and read a SKILL.md only when a task triggers it.
|
|
179
|
+
2. For every non-trivial task, run `/ukit-route <task summary>` for skill + context hints **before** writing code; if it names a skill, read that SKILL.md — do not skip. When `router.enabled: true` in `.ukit/storage/config.json`, prefer router output over guessing; treat it as internal continuation — continue to edit/verify, don't stop.
|
|
240
180
|
|
|
241
181
|
## Project Owner Instructions — Codex and OpenCode
|
|
242
182
|
|
|
@@ -245,13 +185,12 @@ When running in Codex or OpenCode, read and follow the root
|
|
|
245
185
|
project-owner instruction source. Do not copy its contents into this file.
|
|
246
186
|
If it is missing or unreadable, state that limitation and continue with the
|
|
247
187
|
remaining project instructions.
|
|
248
|
-
|
|
188
|
+
<!-- RULES: OWN-01 HOST-OWN-01 -->
|
|
249
189
|
## Skills
|
|
250
190
|
|
|
251
|
-
- Canonical skills live in `.claude/skills
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
- If `opencode.json` ships `ukit-*` commands, treat them as internal helper entrypoints only and **never ask end users to run them**; humans should still only need `ukit install`.
|
|
191
|
+
- Canonical skills live in `.claude/skills/`; adapter mirrors may exist (`.codex/skills/` → symlink). **omp** reads `.claude/skills/` directly via its `claude` discovery provider.
|
|
192
|
+
- **OpenCode**: reads `AGENTS.md` at session start only — it does NOT auto-load skills; the model must explicitly read the triggered SKILL.md.
|
|
193
|
+
- `ukit-*` commands in `opencode.json` are internal helper entrypoints — never ask end users to run them.
|
|
255
194
|
|
|
256
195
|
## Project Snapshot
|
|
257
196
|
|
|
@@ -262,25 +201,13 @@ remaining project instructions.
|
|
|
262
201
|
## Working Rules
|
|
263
202
|
|
|
264
203
|
- Keep scope tight, prefer the smallest correct change set, and reuse existing code.
|
|
265
|
-
-
|
|
266
|
-
- If routed state still says `pull-indexed-context`, treat it as an internal continuation step, not a stopping point.
|
|
267
|
-
- If routed state shows `continuation required` or a stuck-lane rescue mode, finish the named milestone before widening reads or rephrasing the same partial status.
|
|
268
|
-
- Never claim "done", "applied", or "fixed" after Read/Grep/analysis alone. Completion language requires concrete Edit/Write evidence in the current turn, plus verification when the change is risky.
|
|
269
|
-
- Update `docs/WORKLOG.md` after significant work.
|
|
270
|
-
- If source contradicts docs, update docs immediately.
|
|
204
|
+
- Update `docs/WORKLOG.md` after significant work; if source contradicts docs, update docs immediately.
|
|
271
205
|
- Use `{{runtime.packageManager}}`.
|
|
272
206
|
|
|
273
207
|
## DuraOne Skill — Conditional Activation
|
|
208
|
+
<!-- RULE: DURA-01 -->
|
|
274
209
|
|
|
275
|
-
DuraOne skill chỉ active khi pack `duraone` được cài hoặc `.claude/skills/duraone/SKILL.md` tồn tại.
|
|
276
|
-
|
|
277
|
-
- Khi active: luôn đọc `.claude/skills/duraone/SKILL.md` trước khi code.
|
|
278
|
-
- References:
|
|
279
|
-
- `.claude/skills/duraone/references/frontend.md`
|
|
280
|
-
- `.claude/skills/duraone/references/backend.md`
|
|
281
|
-
- `.claude/skills/duraone/references/sql.md`
|
|
282
|
-
- `.claude/skills/duraone/references/workflow.md`
|
|
283
|
-
- Khi không active: dùng generic coding standards + project-specific patterns từ index.
|
|
210
|
+
DuraOne skill chỉ active khi pack `duraone` được cài hoặc `.claude/skills/duraone/SKILL.md` tồn tại — khi active, luôn đọc SKILL.md + references trước khi code; khi không, dùng generic standards + index patterns. Chi tiết: `docs/UKIT_INTERNALS.md`.
|
|
284
211
|
|
|
285
212
|
## Completion Checklist
|
|
286
213
|
|
|
@@ -289,15 +216,12 @@ DuraOne skill chỉ active khi pack `duraone` được cài hoặc `.claude/skil
|
|
|
289
216
|
- Verification executed and reported
|
|
290
217
|
- Docs updated when source truth changed
|
|
291
218
|
|
|
292
|
-
|
|
293
219
|
## Handoff Fullstack Rules
|
|
220
|
+
<!-- RULE: HAND-02 -->
|
|
294
221
|
|
|
295
|
-
- `docs/AI_HANDOFF/RUN.md` là run cursor có thẩm quyền; `Phase:` ≠ `done`/`blocked` nghĩa là run còn sống — Stop gate
|
|
296
|
-
- Recap/checkpoint không bao giờ là completion — chỉ `HANDOFF FULLSTACK COMPLETE` (sau
|
|
297
|
-
-
|
|
298
|
-
- Handoff-create phải viết `docs/AI_HANDOFF/SPEC.md` chi tiết trước khi tạo task; task nào cũng mang `Spec references`.
|
|
299
|
-
- Kết thúc cycle: docs sync → archive `docs/AI_HANDOFF/archive/cycle-NN/` → `Phase: done` → Final Report có marker.
|
|
300
|
-
- `handoff-clear` bắt buộc đóng RUN.md (`Phase: done` hoặc xóa) — cursor sống sẽ giữ Stop gate chặn session sau.
|
|
222
|
+
- `docs/AI_HANDOFF/RUN.md` là run cursor có thẩm quyền; `Phase:` ≠ `done`/`blocked` nghĩa là run còn sống — Stop gate từ chối stop và trả về `Next:` step.
|
|
223
|
+
- Recap/checkpoint không bao giờ là completion — chỉ `HANDOFF FULLSTACK COMPLETE` (sau `Phase: done`) hoặc `HANDOFF FULLSTACK BLOCKED` (sau `Phase: blocked`) mới kết thúc run. Resume tự động mọi task chưa xong (current, legacy, pending, interrupted, recovery `-R<n>`).
|
|
224
|
+
- Kết thúc cycle: docs sync → archive `docs/AI_HANDOFF/archive/cycle-NN/` → `Phase: done`. `handoff-clear` bắt buộc đóng RUN.md. Full rules: `docs/AI_HANDOFF/RULES.md`.
|
|
301
225
|
|
|
302
226
|
## Compact Instructions
|
|
303
227
|
|