@ngockhoale/ukit 1.5.2 → 1.5.4
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 +30 -5
- package/README.md +2 -2
- package/manifests/platform.full.yaml +2 -2
- package/package.json +1 -1
- package/src/cli/commands/doctor.js +14 -2
- package/src/cli/commands/install.js +2 -2
- package/src/cli/commands/uninstall.js +1 -1
- package/src/index/taskRouting.js +117 -1
- package/templates/.claude/hooks/skill-router.sh +1 -1
- package/templates/.claude/hooks/verification-guard.sh +1 -1
- package/templates/.claude/skills/next-step/SKILL.md +1 -1
- package/templates/.claude/ukit/index/post-edit-verify.mjs +3 -2
- package/templates/.claude/ukit/index/route-task.mjs +8 -4
- package/templates/CLAUDE.md +1 -1
- package/templates/docs/AI_HANDOFF/ACTIVE.md +9 -0
- package/templates/docs/AI_HANDOFF/HISTORY.md +4 -0
- package/templates/docs/AI_HANDOFF/INDEX.md +6 -0
- package/templates/docs/AI_HANDOFF/PLAN.md +5 -0
- package/templates/docs/AI_HANDOFF/RULES.md +47 -0
- package/templates/docs/AI_HANDOFF/archive/.gitkeep +0 -0
- package/templates/docs/AI_HANDOFF/tasks/.gitkeep +0 -0
- package/templates/docs/INSTALL.md +2 -2
- package/templates/docs/PROJECT.md +1 -1
- package/templates/docs/UKIT_USAGE_GUIDE.md +1 -1
- package/templates/docs/WORKLOG.md +11 -0
- package/templates/ukit/storage/config.json +1 -1
- package/templates/docs/AI_HANDOFF.md +0 -118
package/CHANGELOG.md
CHANGED
|
@@ -2,23 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 1.5.4 - 2026-05-28
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed stale `docs/AI_HANDOFF.md` references in next-step skill, INSTALL.md, UKIT_USAGE_GUIDE.md, PROJECT.md (template + local).
|
|
10
|
+
- Fixed mirror `route-task.mjs` not showing `handoff=` in summary — wrong intent order (docsSpecific before handoff) + missing handoffFile field + missing display segment.
|
|
11
|
+
- Added `WORKLOG_ARCHIVE.md` to `.gitignore`.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Compacted `docs/MEMORY.md` from 118KB/298 lines to 7.8KB/78 lines. Old entries archived to `docs/MEMORY_ARCHIVE.md`.
|
|
16
|
+
- Cleared handoff task queue after all cycle 002 tasks resolved.
|
|
17
|
+
|
|
18
|
+
## 1.5.3 - 2026-05-28
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Restructured `docs/AI_HANDOFF.md` into `docs/AI_HANDOFF/` folder: ACTIVE.md (snapshot), RULES.md (flow + token budget), PLAN.md (brainstorm), INDEX.md (task index), tasks/ (per-task files), archive/ (completed cycles, max 3).
|
|
23
|
+
- Each task is now an isolated file in `tasks/TASK-xxx.md` — AI reads only the task it implements, not the entire backlog.
|
|
24
|
+
- Added token budget rule: combined handoff reads must stay under 200 lines per request.
|
|
25
|
+
- Added auto-compact rule: if any handoff file exceeds 80 lines, trigger `clear handoff`.
|
|
26
|
+
- Updated template mirror `templates/docs/AI_HANDOFF/` to match new folder structure.
|
|
27
|
+
- Updated `taskRouting.js` handoffFile target from `docs/AI_HANDOFF.md` to `docs/AI_HANDOFF/ACTIVE.md`.
|
|
28
|
+
- Updated `install.js`, `doctor.js`, `uninstall.js` to check for `docs/AI_HANDOFF/ACTIVE.md`.
|
|
29
|
+
|
|
5
30
|
## 1.5.2 - 2026-05-28
|
|
6
31
|
|
|
7
32
|
### Added
|
|
8
33
|
|
|
9
34
|
- Added first-class `ukit:handoff` prompt detection and routing for explicit handoff/brainstorm-to-task flows.
|
|
10
|
-
- Added `intentMode: handoff` to route summaries so hooks and helpers can prioritize
|
|
11
|
-
- Added a reusable generic
|
|
35
|
+
- Added `intentMode: handoff` to route summaries so hooks and helpers can prioritize handoff file automatically.
|
|
36
|
+
- Added a reusable generic handoff template for installed projects.
|
|
12
37
|
|
|
13
38
|
### Changed
|
|
14
39
|
|
|
15
|
-
- Handoff prompts now route through `docs-quality` skill instead of generic `update-status
|
|
16
|
-
- Updated installed `next-step` skill guidance to read
|
|
40
|
+
- Handoff prompts now route through `docs-quality` skill instead of generic `update-status`.
|
|
41
|
+
- Updated installed `next-step` skill guidance to read handoff file first for explicit handoff prompts.
|
|
17
42
|
- Updated `route-task` mirror and hook behavior in both source and installed artifact so `ukit install` users get consistent handoff routing.
|
|
18
43
|
|
|
19
44
|
### Fixed
|
|
20
45
|
|
|
21
|
-
- Handoff authoring is now advisory (exit 0) in Safe Patch, so large
|
|
46
|
+
- Handoff authoring is now advisory (exit 0) in Safe Patch, so large handoff batches no longer block the handoff workflow.
|
|
22
47
|
- Hard runtime/shared-risk file broad rewrites still block by default unless `advisoryOnly=true` or `UKIT_SAFE_PATCH_ADVISORY=1` is set.
|
|
23
48
|
|
|
24
49
|
### Tests
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ ukit install
|
|
|
32
32
|
4. Fill in the generated docs baseline:
|
|
33
33
|
- `docs/PROJECT.md`
|
|
34
34
|
- `docs/MEMORY.md`
|
|
35
|
-
- `docs/AI_HANDOFF
|
|
35
|
+
- `docs/AI_HANDOFF/`
|
|
36
36
|
- `docs/WORKLOG.md`
|
|
37
37
|
5. Open your AI tool and work in natural language.
|
|
38
38
|
|
|
@@ -90,7 +90,7 @@ UKit v1.3.1 keeps the same shared runtime contract while adding Safe Patch Proto
|
|
|
90
90
|
- install globally with `npm install -g @ngockhoale/ukit`
|
|
91
91
|
- keep using the exact same human workflow inside projects: `ukit install`
|
|
92
92
|
- preserve the same `ukit` binary, hooks, and install-first orchestration while standardizing the runtime root as hidden `.ukit/`
|
|
93
|
-
- install `docs/AI_HANDOFF
|
|
93
|
+
- install `docs/AI_HANDOFF/` as the cross-AI handoff folder with per-task isolation: ACTIVE.md (snapshot), INDEX.md (task index), tasks/ (one file per task) so each AI reads only the task it needs, with token budget rules in RULES.md
|
|
94
94
|
- auto-route open-ended “what next?” / “continue” prompts to the `next-step` skill with a visible freshness cue when status may be stale
|
|
95
95
|
- auto-route explicit handoff/wrap-up requests to the `update-status` skill while skipping trivial/no-state-change tasks
|
|
96
96
|
- keep concrete debug/implementation/review prompts primary, so project status never replaces source/index-first task work
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
2
3
|
import { pathExists, readJsonIfExists } from '../../core/fileOps.js';
|
|
3
4
|
import { buildPathConfig } from '../../core/paths.js';
|
|
4
5
|
import { buildRuntimePaths } from '../../core/runtimePaths.js';
|
|
@@ -45,6 +46,15 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
45
46
|
: [];
|
|
46
47
|
const codexAdapterTracked = trackedPaths.some((entry) => entry.startsWith('.codex/'));
|
|
47
48
|
|
|
49
|
+
const WORKLOG_MAX_LINES = 600;
|
|
50
|
+
let worklogLineCount = 0;
|
|
51
|
+
try {
|
|
52
|
+
const content = await fs.readFile(path.join(projectRoot, 'docs', 'WORKLOG.md'), 'utf8');
|
|
53
|
+
worklogLineCount = content.split('\n').length;
|
|
54
|
+
} catch {
|
|
55
|
+
// file may not exist
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
const checks = {
|
|
49
59
|
manifestLoaded: Boolean(manifest?.name),
|
|
50
60
|
templatesDirExists: await pathExists(pathConfig.templatesRoot),
|
|
@@ -62,8 +72,9 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
62
72
|
sessionMemoryDirExists: await pathExists(runtimePaths.sessionsDir),
|
|
63
73
|
docsProjectExists: await pathExists(path.join(projectRoot, 'docs', 'PROJECT.md')),
|
|
64
74
|
docsMemoryExists: await pathExists(path.join(projectRoot, 'docs', 'MEMORY.md')),
|
|
65
|
-
docsAiHandoffExists: await pathExists(path.join(projectRoot, 'docs', 'AI_HANDOFF.md')),
|
|
75
|
+
docsAiHandoffExists: await pathExists(path.join(projectRoot, 'docs', 'AI_HANDOFF', 'ACTIVE.md')),
|
|
66
76
|
docsWorklogExists: await pathExists(path.join(projectRoot, 'docs', 'WORKLOG.md')),
|
|
77
|
+
docsWorklogUnderBudget: worklogLineCount <= WORKLOG_MAX_LINES,
|
|
67
78
|
allProvidersConfigured: providers.allSupported,
|
|
68
79
|
...(codexAdapterTracked
|
|
69
80
|
? {
|
|
@@ -104,8 +115,9 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
104
115
|
console.log(`[UKit] ${ok(checks.sessionMemoryDirExists)} .ukit/storage/memory/sessions/`);
|
|
105
116
|
console.log(`[UKit] ${ok(checks.docsProjectExists)} docs/PROJECT.md`);
|
|
106
117
|
console.log(`[UKit] ${ok(checks.docsMemoryExists)} docs/MEMORY.md`);
|
|
107
|
-
console.log(`[UKit] ${ok(checks.docsAiHandoffExists)} docs/AI_HANDOFF
|
|
118
|
+
console.log(`[UKit] ${ok(checks.docsAiHandoffExists)} docs/AI_HANDOFF/`);
|
|
108
119
|
console.log(`[UKit] ${ok(checks.docsWorklogExists)} docs/WORKLOG.md`);
|
|
120
|
+
console.log(`[UKit] ${ok(checks.docsWorklogUnderBudget)} docs/WORKLOG.md under budget (${worklogLineCount}/${WORKLOG_MAX_LINES} lines)`);
|
|
109
121
|
if (codexAdapterTracked) {
|
|
110
122
|
console.log(`[UKit] ${ok(checks.codexReadmeExists)} .codex/README.md`);
|
|
111
123
|
console.log(`[UKit] ${ok(checks.codexSettingsExists)} .codex/settings.json`);
|
|
@@ -239,7 +239,7 @@ export async function runInstall({ packageRoot, projectRoot, packageVersion, arg
|
|
|
239
239
|
const docsLabels = [
|
|
240
240
|
'docs/PROJECT.md',
|
|
241
241
|
'docs/MEMORY.md',
|
|
242
|
-
'docs/AI_HANDOFF.md',
|
|
242
|
+
'docs/AI_HANDOFF/ACTIVE.md',
|
|
243
243
|
'docs/WORKLOG.md',
|
|
244
244
|
];
|
|
245
245
|
|
|
@@ -254,7 +254,7 @@ export async function runInstall({ packageRoot, projectRoot, packageVersion, arg
|
|
|
254
254
|
if (missingDocs.length > 0) {
|
|
255
255
|
console.log(`[UKit] Missing docs — fill these in before first use: ${missingDocs.join(', ')}`);
|
|
256
256
|
} else {
|
|
257
|
-
console.log('[UKit] Docs baseline ready: docs/PROJECT.md, docs/MEMORY.md, docs/AI_HANDOFF
|
|
257
|
+
console.log('[UKit] Docs baseline ready: docs/PROJECT.md, docs/MEMORY.md, docs/AI_HANDOFF/, docs/WORKLOG.md');
|
|
258
258
|
console.log('[UKit] Fill them once with real project context for the best results.');
|
|
259
259
|
}
|
|
260
260
|
|
|
@@ -47,5 +47,5 @@ export async function runUninstall({ projectRoot, argv = [] }) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
console.log(`[UKit] Uninstall complete. Removed ${result.removed}/${result.attempted} managed paths.`);
|
|
50
|
-
console.log('[UKit] Note: docs/PROJECT.md, docs/MEMORY.md, docs/AI_HANDOFF
|
|
50
|
+
console.log('[UKit] Note: docs/PROJECT.md, docs/MEMORY.md, docs/AI_HANDOFF/, docs/WORKLOG.md contain user content and were preserved. Delete manually if needed.');
|
|
51
51
|
}
|
package/src/index/taskRouting.js
CHANGED
|
@@ -139,6 +139,10 @@ export async function deriveTaskRoute({
|
|
|
139
139
|
contextRecommendation,
|
|
140
140
|
verificationRecommendation,
|
|
141
141
|
});
|
|
142
|
+
const handoffBudget = intentMode === 'handoff'
|
|
143
|
+
? await checkHandoffBudget(absoluteRoot)
|
|
144
|
+
: null;
|
|
145
|
+
const worklogBudget = await checkWorklogBudget(absoluteRoot);
|
|
142
146
|
const routeSummary = buildRouteSummary({
|
|
143
147
|
activeSkills,
|
|
144
148
|
routingContext: {
|
|
@@ -157,6 +161,8 @@ export async function deriveTaskRoute({
|
|
|
157
161
|
contextRecommendation,
|
|
158
162
|
verificationRecommendation,
|
|
159
163
|
nextAction,
|
|
164
|
+
handoffBudget,
|
|
165
|
+
worklogBudget,
|
|
160
166
|
});
|
|
161
167
|
const approachSelector = routeSummary?.approachSelector ?? null;
|
|
162
168
|
|
|
@@ -180,6 +186,8 @@ export async function deriveTaskRoute({
|
|
|
180
186
|
verificationRecommendation,
|
|
181
187
|
nextAction,
|
|
182
188
|
routeSummary,
|
|
189
|
+
handoffBudget,
|
|
190
|
+
worklogBudget,
|
|
183
191
|
...(degradedWarnings.length > 0 ? { degradedWarnings } : {}),
|
|
184
192
|
};
|
|
185
193
|
}
|
|
@@ -190,6 +198,8 @@ export function buildRouteSummary({
|
|
|
190
198
|
contextRecommendation = null,
|
|
191
199
|
verificationRecommendation = null,
|
|
192
200
|
nextAction = null,
|
|
201
|
+
handoffBudget = null,
|
|
202
|
+
worklogBudget = null,
|
|
193
203
|
} = {}) {
|
|
194
204
|
const autonomyLevel = routingContext.autonomyLevel ?? 'balanced';
|
|
195
205
|
const delegationRecommendation = deriveDelegationRecommendation({
|
|
@@ -243,7 +253,7 @@ export function buildRouteSummary({
|
|
|
243
253
|
),
|
|
244
254
|
);
|
|
245
255
|
const nextActionCommand = compactHelperLane ? null : nextAction?.command ?? null;
|
|
246
|
-
const handoffFile = routingContext.intentMode === 'handoff' ? 'docs/AI_HANDOFF.md' : null;
|
|
256
|
+
const handoffFile = routingContext.intentMode === 'handoff' ? 'docs/AI_HANDOFF/ACTIVE.md' : null;
|
|
247
257
|
const summaryLine = [
|
|
248
258
|
routingContext.taskType ? `task=${routingContext.taskType}` : null,
|
|
249
259
|
handoffFile ? `handoff=${handoffFile}` : null,
|
|
@@ -253,6 +263,8 @@ export function buildRouteSummary({
|
|
|
253
263
|
editGuardHint ? `editGuard=${editGuardHint}` : null,
|
|
254
264
|
delegationRecommendation?.hint ? `delegate=${delegationRecommendation.hint}` : null,
|
|
255
265
|
policyMode ? `policy=${policyMode}` : null,
|
|
266
|
+
handoffBudget?.warning ? `budget=${handoffBudget.warning}` : null,
|
|
267
|
+
worklogBudget?.warning ? `budget=${worklogBudget.warning}` : null,
|
|
256
268
|
].filter(Boolean).join(' | ');
|
|
257
269
|
|
|
258
270
|
return {
|
|
@@ -270,6 +282,8 @@ export function buildRouteSummary({
|
|
|
270
282
|
continuationState,
|
|
271
283
|
intentMode: routingContext.intentMode ?? null,
|
|
272
284
|
handoffFile,
|
|
285
|
+
handoffBudget,
|
|
286
|
+
worklogBudget,
|
|
273
287
|
delegateHint: delegationRecommendation?.hint ?? null,
|
|
274
288
|
nextActionType: nextAction?.type ?? null,
|
|
275
289
|
nextActionCommand,
|
|
@@ -1480,6 +1494,108 @@ function unique(values) {
|
|
|
1480
1494
|
return [...new Set(values.filter(Boolean))];
|
|
1481
1495
|
}
|
|
1482
1496
|
|
|
1497
|
+
const HANDOFF_BUDGET_MAX_LINES = 200;
|
|
1498
|
+
const HANDOFF_FILE_MAX_LINES = 80;
|
|
1499
|
+
const WORKLOG_BUDGET_MAX_LINES = 600;
|
|
1500
|
+
const WORKLOG_BUDGET_MAX_ENTRIES = 30;
|
|
1501
|
+
|
|
1502
|
+
export async function checkHandoffBudget(rootDir) {
|
|
1503
|
+
const handoffDir = path.join(rootDir, 'docs', 'AI_HANDOFF');
|
|
1504
|
+
const checkFiles = ['ACTIVE.md', 'INDEX.md'];
|
|
1505
|
+
let totalLines = 0;
|
|
1506
|
+
const fileDetails = [];
|
|
1507
|
+
|
|
1508
|
+
for (const fileName of checkFiles) {
|
|
1509
|
+
const filePath = path.join(handoffDir, fileName);
|
|
1510
|
+
try {
|
|
1511
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
1512
|
+
const lines = content.split('\n').length;
|
|
1513
|
+
totalLines += lines;
|
|
1514
|
+
fileDetails.push({ file: fileName, lines });
|
|
1515
|
+
} catch {
|
|
1516
|
+
// File may not exist yet — not an error
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// Also count task files
|
|
1521
|
+
const tasksDir = path.join(handoffDir, 'tasks');
|
|
1522
|
+
try {
|
|
1523
|
+
const taskFiles = await fs.readdir(tasksDir);
|
|
1524
|
+
for (const taskFile of taskFiles) {
|
|
1525
|
+
if (!taskFile.endsWith('.md')) continue;
|
|
1526
|
+
const content = await fs.readFile(path.join(tasksDir, taskFile), 'utf8');
|
|
1527
|
+
const lines = content.split('\n').length;
|
|
1528
|
+
totalLines += lines;
|
|
1529
|
+
fileDetails.push({ file: `tasks/${taskFile}`, lines });
|
|
1530
|
+
}
|
|
1531
|
+
} catch {
|
|
1532
|
+
// tasks dir may not exist
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
const oversizedFiles = fileDetails.filter((f) => f.lines > HANDOFF_FILE_MAX_LINES);
|
|
1536
|
+
const overBudget = totalLines > HANDOFF_BUDGET_MAX_LINES;
|
|
1537
|
+
|
|
1538
|
+
let warning = null;
|
|
1539
|
+
if (overBudget) {
|
|
1540
|
+
warning = 'over-budget';
|
|
1541
|
+
} else if (oversizedFiles.length > 0) {
|
|
1542
|
+
warning = 'file-oversized';
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
return {
|
|
1546
|
+
totalLines,
|
|
1547
|
+
maxLines: HANDOFF_BUDGET_MAX_LINES,
|
|
1548
|
+
fileDetails,
|
|
1549
|
+
oversizedFiles: oversizedFiles.map((f) => f.file),
|
|
1550
|
+
overBudget,
|
|
1551
|
+
warning,
|
|
1552
|
+
action: warning ? 'clear-handoff' : null,
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
export async function checkWorklogBudget(rootDir) {
|
|
1557
|
+
const worklogPath = path.join(rootDir, 'docs', 'WORKLOG.md');
|
|
1558
|
+
try {
|
|
1559
|
+
const content = await fs.readFile(worklogPath, 'utf8');
|
|
1560
|
+
const lines = content.split('\n').length;
|
|
1561
|
+
const entryMatches = content.match(/^## \d{4}-\d{2}-\d{2}/gm) ?? [];
|
|
1562
|
+
const entryCount = entryMatches.length;
|
|
1563
|
+
|
|
1564
|
+
const overLineBudget = lines > WORKLOG_BUDGET_MAX_LINES;
|
|
1565
|
+
const overEntryBudget = entryCount > WORKLOG_BUDGET_MAX_ENTRIES;
|
|
1566
|
+
const overBudget = overLineBudget || overEntryBudget;
|
|
1567
|
+
|
|
1568
|
+
let warning = null;
|
|
1569
|
+
if (overLineBudget && overEntryBudget) {
|
|
1570
|
+
warning = 'worklog-lines-and-entries-over';
|
|
1571
|
+
} else if (overLineBudget) {
|
|
1572
|
+
warning = 'worklog-lines-over';
|
|
1573
|
+
} else if (overEntryBudget) {
|
|
1574
|
+
warning = 'worklog-entries-over';
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
return {
|
|
1578
|
+
lines,
|
|
1579
|
+
maxLines: WORKLOG_BUDGET_MAX_LINES,
|
|
1580
|
+
entryCount,
|
|
1581
|
+
maxEntries: WORKLOG_BUDGET_MAX_ENTRIES,
|
|
1582
|
+
overBudget,
|
|
1583
|
+
warning,
|
|
1584
|
+
action: warning ? 'compact-worklog' : null,
|
|
1585
|
+
};
|
|
1586
|
+
} catch {
|
|
1587
|
+
return {
|
|
1588
|
+
lines: 0,
|
|
1589
|
+
maxLines: WORKLOG_BUDGET_MAX_LINES,
|
|
1590
|
+
entryCount: 0,
|
|
1591
|
+
maxEntries: WORKLOG_BUDGET_MAX_ENTRIES,
|
|
1592
|
+
overBudget: false,
|
|
1593
|
+
warning: null,
|
|
1594
|
+
action: null,
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1483
1599
|
const DELEGATABLE_IMPLEMENTATION_SKILL_IDS = new Set([
|
|
1484
1600
|
'delivery',
|
|
1485
1601
|
'frontend',
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Used from UserPromptSubmit and PreToolUse so end users do not need to name skills.
|
|
4
4
|
|
|
5
5
|
INPUT=$(cat)
|
|
6
|
-
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$
|
|
6
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
7
7
|
STATE_FILE="$PROJECT_ROOT/.claude/ukit/skill-router-state.json"
|
|
8
8
|
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
9
|
THRESHOLD_SCRIPT="$HOOK_DIR/../ukit/runtime/compact-threshold.mjs"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# and does not jump to blanket verification when targeted evidence already exists.
|
|
4
4
|
|
|
5
5
|
INPUT=$(cat)
|
|
6
|
-
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$
|
|
6
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
7
7
|
STATE_FILE="$PROJECT_ROOT/.claude/ukit/skill-router-state.json"
|
|
8
8
|
ROUTE_CACHE_FILE="$PROJECT_ROOT/.claude/ukit/route-cache.json"
|
|
9
9
|
PROGRESS_FILE="$PROJECT_ROOT/.claude/ukit/verification-progress.json"
|
|
@@ -44,7 +44,7 @@ If stale or missing, downgrade confidence and verify with the smallest current t
|
|
|
44
44
|
## Input Order
|
|
45
45
|
|
|
46
46
|
Read only what is needed:
|
|
47
|
-
1. `docs/AI_HANDOFF.md` first when the prompt explicitly names handoff / `ukit:handoff` / brainstorm-to-task flow
|
|
47
|
+
1. `docs/AI_HANDOFF/ACTIVE.md` first when the prompt explicitly names handoff / `ukit:handoff` / brainstorm-to-task flow
|
|
48
48
|
2. `docs/STATUS.md` (or existing root `STATUS.md` fallback) for normal status/continue prompts
|
|
49
49
|
3. `docs/TASKS.md` only for queued-task prompts, “continue” with no active status, or when status points at queued work
|
|
50
50
|
4. `docs/CODE_MAP.md` only when navigation is needed
|
|
@@ -18,7 +18,8 @@ function getFilePath(payload = {}) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function isHandoffAuthoringFile(relativePath) {
|
|
21
|
-
return relativePath === 'docs/AI_HANDOFF.md'
|
|
21
|
+
return relativePath === 'docs/AI_HANDOFF.md'
|
|
22
|
+
|| relativePath.startsWith('docs/AI_HANDOFF/');
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
async function listManifestPaths(backupsRoot) {
|
|
@@ -200,7 +201,7 @@ async function main() {
|
|
|
200
201
|
process.stdout.write(`[ukit-safe-patch] ${result.message}\n`);
|
|
201
202
|
} else if (result.status === 'advisory') {
|
|
202
203
|
process.stderr.write(`[ukit-safe-patch] ${result.message}\n`);
|
|
203
|
-
process.stderr.write('[ukit-safe-patch] handoff authoring advisory — change is already written; continue updating docs/AI_HANDOFF
|
|
204
|
+
process.stderr.write('[ukit-safe-patch] handoff authoring advisory — change is already written; continue updating docs/AI_HANDOFF/.\n');
|
|
204
205
|
}
|
|
205
206
|
if (result.status === 'blocked') {
|
|
206
207
|
const advisory = isSafePatchAdvisoryOnly(runtimeConfig);
|
|
@@ -481,6 +481,7 @@ function formatDisplayRouteSummary(routeSummary = null, routingContext = {}) {
|
|
|
481
481
|
|
|
482
482
|
const segments = [
|
|
483
483
|
taskSegment,
|
|
484
|
+
extractRouteLineSegment(line, 'handoff'),
|
|
484
485
|
extractRouteLineSegment(line, 'targets'),
|
|
485
486
|
extractRouteLineSegment(line, 'tests'),
|
|
486
487
|
extractRouteLineSegment(line, 'styles'),
|
|
@@ -831,14 +832,14 @@ function deriveIntentMode({ promptText = '', commandText = '', targetFile = null
|
|
|
831
832
|
const openEndedStatus = hasOpenEndedStatusSignal(lower, raw) || taskQueueNext;
|
|
832
833
|
const concreteTask = hasConcreteTaskSignal(lower, raw, targetFile, { taskQueueNext });
|
|
833
834
|
|
|
834
|
-
if (docsSpecific) {
|
|
835
|
-
return 'docs-specific';
|
|
836
|
-
}
|
|
837
|
-
|
|
838
835
|
if (hasHandoffSignal(lower, raw)) {
|
|
839
836
|
return 'handoff';
|
|
840
837
|
}
|
|
841
838
|
|
|
839
|
+
if (docsSpecific) {
|
|
840
|
+
return 'docs-specific';
|
|
841
|
+
}
|
|
842
|
+
|
|
842
843
|
if (statusUpdate) {
|
|
843
844
|
return 'status-update';
|
|
844
845
|
}
|
|
@@ -1350,8 +1351,10 @@ function buildRouteSummary({
|
|
|
1350
1351
|
),
|
|
1351
1352
|
);
|
|
1352
1353
|
const nextActionCommand = compactHelperLane ? null : nextAction?.command ?? null;
|
|
1354
|
+
const handoffFile = routingContext.intentMode === 'handoff' ? 'docs/AI_HANDOFF/ACTIVE.md' : null;
|
|
1353
1355
|
const line = [
|
|
1354
1356
|
routingContext.taskType ? `task=${routingContext.taskType}` : null,
|
|
1357
|
+
handoffFile ? `handoff=${handoffFile}` : null,
|
|
1355
1358
|
formatCompactSegment('targets', primaryTargets),
|
|
1356
1359
|
formatCompactSegment('tests', relatedTests),
|
|
1357
1360
|
formatCompactSegment('styles', styleFiles),
|
|
@@ -1374,6 +1377,7 @@ function buildRouteSummary({
|
|
|
1374
1377
|
completionState,
|
|
1375
1378
|
continuationState,
|
|
1376
1379
|
intentMode: routingContext.intentMode ?? null,
|
|
1380
|
+
handoffFile,
|
|
1377
1381
|
delegateHint: delegationRecommendation?.hint ?? null,
|
|
1378
1382
|
nextActionType: nextAction?.type ?? null,
|
|
1379
1383
|
nextActionCommand,
|
package/templates/CLAUDE.md
CHANGED
|
@@ -96,7 +96,7 @@ For clearly non-code specialist lanes (docs-only, status, task queue), skip the
|
|
|
96
96
|
- **Non-trivial**: `docs/MEMORY.md` + `docs/PROJECT.md` + `docs/CODE_MAP.md`.
|
|
97
97
|
- `docs/STATUS.md`: use for open-ended status/continue prompts or meaningful continuation context; stale status is orientation only.
|
|
98
98
|
- `docs/TASKS.md`: use only for queued-task prompts or when status points at queued work; safely clean exact duplicates/completed overflow by default without deleting unfinished human-authored tasks.
|
|
99
|
-
- `docs/WORKLOG.md`: only recent relevant entries.
|
|
99
|
+
- `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.
|
|
100
100
|
- Follow routed verification policy: targeted first, widen only when risk/shared scope justifies it, ask before blanket broad runs.
|
|
101
101
|
|
|
102
102
|
## Living Status Workflow
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Handoff Rules
|
|
2
|
+
|
|
3
|
+
## Token Budget (MANDATORY)
|
|
4
|
+
|
|
5
|
+
- **Combined handoff reads must stay under 200 lines per request.**
|
|
6
|
+
- Read order: `ACTIVE.md` (if needed) → `INDEX.md` (scan tasks) → single `tasks/TASK-xxx.md` (implement one task).
|
|
7
|
+
- Do NOT read `RULES.md` every request — only when you need flow clarification.
|
|
8
|
+
- Do NOT read multiple task files in one request.
|
|
9
|
+
- If ACTIVE.md + INDEX.md + task file would exceed budget, read only the task file.
|
|
10
|
+
- Auto-compact: if any single handoff file exceeds 80 lines, trigger `clear handoff` immediately.
|
|
11
|
+
|
|
12
|
+
## How Human Submits Ideas
|
|
13
|
+
|
|
14
|
+
- Natural language is enough: `ukit:handoff`, `gom ý tưởng`, `chia task`, `đưa vào handoff`.
|
|
15
|
+
- If request is already a concrete task (clear file/logic/output, small enough to do now), bypass handoff and execute directly.
|
|
16
|
+
- If request is broad/ambiguous/multi-step, use handoff.
|
|
17
|
+
|
|
18
|
+
## Handoff Flow
|
|
19
|
+
|
|
20
|
+
1. Human submits ideas → AI writes to `PLAN.md`.
|
|
21
|
+
2. Human approves plan → AI splits into `tasks/TASK-xxx.md` + updates `INDEX.md`.
|
|
22
|
+
3. AI implementer reads `INDEX.md` → picks task → reads `tasks/TASK-xxx.md` → implements.
|
|
23
|
+
4. After implementation → update `INDEX.md` status, archive cycle if done.
|
|
24
|
+
|
|
25
|
+
## Task Gate
|
|
26
|
+
|
|
27
|
+
A task is `ready` only when it has:
|
|
28
|
+
- Clear target files
|
|
29
|
+
- Clear action
|
|
30
|
+
- Dependencies stated
|
|
31
|
+
- Verification command
|
|
32
|
+
- Acceptance criteria
|
|
33
|
+
|
|
34
|
+
Missing any → `needs_breakdown`, `blocked`, or `needs_human`.
|
|
35
|
+
|
|
36
|
+
## Clear Handoff
|
|
37
|
+
|
|
38
|
+
1. Archive current cycle → `archive/cycle-NNN.md`.
|
|
39
|
+
2. If archive > 3 files → delete oldest, append 1-line summary to `HISTORY.md`.
|
|
40
|
+
3. Reset `ACTIVE.md` to empty template.
|
|
41
|
+
4. Clear `INDEX.md`.
|
|
42
|
+
5. Delete all files in `tasks/`.
|
|
43
|
+
6. Clear `PLAN.md`.
|
|
44
|
+
|
|
45
|
+
## Docs Sync
|
|
46
|
+
|
|
47
|
+
After cycle, update affected docs only: `WORKLOG.md`, `PROJECT.md`, `CODE_MAP.md`, `CHANGELOG.md`.
|
|
File without changes
|
|
File without changes
|
|
@@ -47,7 +47,7 @@ End users do not need to manage any of that manually.
|
|
|
47
47
|
Complete these files before first serious use:
|
|
48
48
|
- `docs/PROJECT.md`
|
|
49
49
|
- `docs/MEMORY.md`
|
|
50
|
-
- `docs/AI_HANDOFF
|
|
50
|
+
- `docs/AI_HANDOFF/`
|
|
51
51
|
- `docs/WORKLOG.md`
|
|
52
52
|
|
|
53
53
|
### 4) Open your AI tool
|
|
@@ -97,7 +97,7 @@ ukit install
|
|
|
97
97
|
Check that the docs baseline files exist and are filled in:
|
|
98
98
|
- `docs/PROJECT.md`
|
|
99
99
|
- `docs/MEMORY.md`
|
|
100
|
-
- `docs/AI_HANDOFF
|
|
100
|
+
- `docs/AI_HANDOFF/`
|
|
101
101
|
- `docs/WORKLOG.md`
|
|
102
102
|
|
|
103
103
|
---
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
1. Run `ukit memory recall "<current task>"` for non-trivial work; reuse relevant `## Previous Context` before asking the user to restate prior decisions
|
|
46
46
|
2. Read `docs/MEMORY.md` — architecture decisions, active constraints, known bugs
|
|
47
|
-
3. Read `docs/AI_HANDOFF.md` when continuing cross-AI planning, task breakdown, or task implementation handoff work
|
|
47
|
+
3. Read `docs/AI_HANDOFF/ACTIVE.md` when continuing cross-AI planning, task breakdown, or task implementation handoff work
|
|
48
48
|
4. Read `docs/CODE_MAP.md` if it exists — structural navigation index
|
|
49
49
|
5. Use the installed source-code index / routed helpers to localize the smallest relevant file + test set first
|
|
50
50
|
6. Scan recent `docs/WORKLOG.md` entries if continuing prior work
|
|
@@ -138,7 +138,7 @@ Project đang ở đâu, làm gì tiếp?
|
|
|
138
138
|
|
|
139
139
|
Expected UKit behavior:
|
|
140
140
|
1. auto-load the hidden next-step lane
|
|
141
|
-
2. read `docs/AI_HANDOFF.md` when the team is passing planning, task breakdown, or implementation context between AIs
|
|
141
|
+
2. read `docs/AI_HANDOFF/ACTIVE.md` when the team is passing planning, task breakdown, or implementation context between AIs
|
|
142
142
|
3. verify the handoff against source/index before treating it as authoritative
|
|
143
143
|
4. suggest only a few actionable next candidates
|
|
144
144
|
5. if the prompt names a concrete bug/feature/review target, keep the concrete workflow primary instead of producing a global roadmap
|
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Track session-level execution details.
|
|
4
4
|
|
|
5
|
+
## Budget Rules
|
|
6
|
+
|
|
7
|
+
Keep this file compact to save AI context tokens:
|
|
8
|
+
|
|
9
|
+
- **Max 30 entries.** When over, archive the oldest entries to `docs/WORKLOG_ARCHIVE.md`.
|
|
10
|
+
- **Max ~600 lines.** If over, archive oldest entries until under budget.
|
|
11
|
+
- Each entry should be 10-20 lines max (summary, not transcript).
|
|
12
|
+
- On archive: move full entry block to `docs/WORKLOG_ARCHIVE.md` (create if missing).
|
|
13
|
+
- Keep a compaction marker as the last line: `<!-- Entries before YYYY-MM archived to docs/WORKLOG_ARCHIVE.md. Keep this file < 600 lines. -->`
|
|
14
|
+
- If the user says "compact worklog" or "clean worklog", perform the archive pass and report what moved.
|
|
15
|
+
|
|
5
16
|
For each significant action, append:
|
|
6
17
|
- Date/time
|
|
7
18
|
- Action taken
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
# AI Handoff — {{project.name}}
|
|
2
|
-
|
|
3
|
-
Dùng file này làm active handoff duy nhất cho project.
|
|
4
|
-
Source code và test vẫn là sự thật cuối cùng.
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## 1. Current Cycle Snapshot
|
|
9
|
-
|
|
10
|
-
- Handoff status: `draft | ready_for_planning | ready_for_breakdown | ready_for_implementation | blocked | ready_for_next_cycle | done`
|
|
11
|
-
- Current phase: `planning | breakdown | implementation | verification | wrap_up | cleared`
|
|
12
|
-
- Updated at:
|
|
13
|
-
- Updated by AI:
|
|
14
|
-
- Human decision needed: `yes | no`
|
|
15
|
-
- Human decision summary:
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## 2. Handoff Rules
|
|
20
|
-
|
|
21
|
-
### How human should submit ideas
|
|
22
|
-
- Có thể nói tự nhiên: `ukit:handoff`, `đưa vào handoff`, `gom các ý tưởng này thành task`, `chia việc cho AI làm tiếp`.
|
|
23
|
-
- Nếu request đã là task cụ thể, rõ file/logic/đầu ra hoặc đủ nhỏ để làm ngay, bypass handoff và thực hiện trực tiếp.
|
|
24
|
-
- Nếu request là ý tưởng rộng, nhiều tính năng, cải tiến mơ hồ, brainstorming, nhiều plan/task, hoặc cần chia cho AI kế tiếp, dùng handoff.
|
|
25
|
-
|
|
26
|
-
### Default handoff flow
|
|
27
|
-
1. Capture ideas/request.
|
|
28
|
-
2. Write a concrete plan.
|
|
29
|
-
3. Split the plan into ordered tasks.
|
|
30
|
-
4. Add target files, verification, dependencies, acceptance criteria, and stop conditions.
|
|
31
|
-
5. Resolve `ready` tasks in order.
|
|
32
|
-
6. Update this file with results.
|
|
33
|
-
7. Clear active handoff by keeping summary + carry-forward backlog only.
|
|
34
|
-
|
|
35
|
-
### Fast-AI task gate
|
|
36
|
-
Một task chỉ được là `ready` khi có:
|
|
37
|
-
- target file/folder rõ;
|
|
38
|
-
- action cụ thể;
|
|
39
|
-
- dependency/blocker rõ;
|
|
40
|
-
- verification rõ;
|
|
41
|
-
- acceptance criteria rõ;
|
|
42
|
-
- stop condition rõ;
|
|
43
|
-
- không cần đoán product decision.
|
|
44
|
-
|
|
45
|
-
Nếu thiếu, để `needs_breakdown`, `blocked`, hoặc `needs_human`.
|
|
46
|
-
|
|
47
|
-
### Clear rule
|
|
48
|
-
- `clear handoff` không bao giờ là wipe mù.
|
|
49
|
-
- Trước khi clear, AI phải resolve hoặc triage task còn lại.
|
|
50
|
-
- Completed work thành summary ngắn.
|
|
51
|
-
- Unfinished work thành carry-forward backlog ngắn.
|
|
52
|
-
- Sau clear, file phải ngắn, sẵn sàng cho cycle tiếp theo.
|
|
53
|
-
|
|
54
|
-
### Docs sync rule
|
|
55
|
-
- Handoff không thay thế docs.
|
|
56
|
-
- Sau khi xong cycle, update các file docs trực tiếp bị ảnh hưởng như `docs/WORKLOG.md`, `docs/PROJECT.md`, `docs/CODE_MAP.md`, `CHANGELOG.md` nếu có liên quan.
|
|
57
|
-
- Không cần update toàn bộ `docs/` nếu không bị ảnh hưởng.
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## 3. Completed Cycle Summary
|
|
62
|
-
|
|
63
|
-
### Cycle summary
|
|
64
|
-
- completed:
|
|
65
|
-
- verification:
|
|
66
|
-
- important decisions:
|
|
67
|
-
- carry-forward needed:
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## 4. Carry-Forward Backlog
|
|
72
|
-
|
|
73
|
-
### TASK-001
|
|
74
|
-
- Title:
|
|
75
|
-
- Priority: `P0-now | P1-next | P2-later | blocked | needs_human`
|
|
76
|
-
- Size: `S | M | L`
|
|
77
|
-
- AI capability: `fast_ai_ok | needs_smart_ai | needs_human`
|
|
78
|
-
- Goal:
|
|
79
|
-
- Target files:
|
|
80
|
-
- Dependencies:
|
|
81
|
-
- Must do:
|
|
82
|
-
- Must not do:
|
|
83
|
-
- Acceptance criteria:
|
|
84
|
-
- Verification:
|
|
85
|
-
- Stop condition:
|
|
86
|
-
- Status: `ready | blocked | in_progress | done | needs_breakdown | needs_human`
|
|
87
|
-
- Owner AI:
|
|
88
|
-
|
|
89
|
-
### TASK-002
|
|
90
|
-
- Title:
|
|
91
|
-
- Priority: `P0-now | P1-next | P2-later | blocked | needs_human`
|
|
92
|
-
- Size: `S | M | L`
|
|
93
|
-
- AI capability: `fast_ai_ok | needs_smart_ai | needs_human`
|
|
94
|
-
- Goal:
|
|
95
|
-
- Target files:
|
|
96
|
-
- Dependencies:
|
|
97
|
-
- Must do:
|
|
98
|
-
- Must not do:
|
|
99
|
-
- Acceptance criteria:
|
|
100
|
-
- Verification:
|
|
101
|
-
- Stop condition:
|
|
102
|
-
- Status: `ready | blocked | in_progress | done | needs_breakdown | needs_human`
|
|
103
|
-
- Owner AI:
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## 5. Decisions Made By Human
|
|
108
|
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## 6. Do Not Lose This Context
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
-
|