@ngockhoale/ukit 1.5.1 → 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 +49 -0
- package/README.md +3 -5
- package/manifests/platform.full.yaml +5 -20
- package/package.json +1 -1
- package/src/cli/commands/doctor.js +14 -4
- package/src/cli/commands/install.js +3 -3
- package/src/cli/commands/uninstall.js +1 -1
- package/src/core/ensureGitignore.js +0 -2
- package/src/core/runtimeConfig.js +1 -1
- package/src/index/taskRouting.js +139 -2
- 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 +6 -5
- package/templates/.claude/ukit/index/post-edit-verify.mjs +12 -3
- package/templates/.claude/ukit/index/route-task.mjs +24 -2
- 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 -4
- package/templates/docs/PROJECT.md +5 -6
- package/templates/docs/UKIT_USAGE_GUIDE.md +3 -4
- package/templates/docs/WORKLOG.md +11 -0
- package/templates/ukit/storage/config.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,55 @@
|
|
|
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
|
+
|
|
30
|
+
## 1.5.2 - 2026-05-28
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- Added first-class `ukit:handoff` prompt detection and routing for explicit handoff/brainstorm-to-task flows.
|
|
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.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
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.
|
|
42
|
+
- Updated `route-task` mirror and hook behavior in both source and installed artifact so `ukit install` users get consistent handoff routing.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Handoff authoring is now advisory (exit 0) in Safe Patch, so large handoff batches no longer block the handoff workflow.
|
|
47
|
+
- Hard runtime/shared-risk file broad rewrites still block by default unless `advisoryOnly=true` or `UKIT_SAFE_PATCH_ADVISORY=1` is set.
|
|
48
|
+
|
|
49
|
+
### Tests
|
|
50
|
+
|
|
51
|
+
- Added explicit handoff routing coverage in `tests/index/taskRouting.test.js`.
|
|
52
|
+
- Verified Safe Patch protocol still passes 12 tests including the new handoff advisory case.
|
|
53
|
+
|
|
5
54
|
## 1.5.0 - 2026-05-24
|
|
6
55
|
|
|
7
56
|
### Fixed
|
package/README.md
CHANGED
|
@@ -32,8 +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/
|
|
36
|
-
- `docs/TASKS.md`
|
|
35
|
+
- `docs/AI_HANDOFF/`
|
|
37
36
|
- `docs/WORKLOG.md`
|
|
38
37
|
5. Open your AI tool and work in natural language.
|
|
39
38
|
|
|
@@ -66,7 +65,7 @@ If maintainers roll out a newer CLI build, the in-project workflow still stays t
|
|
|
66
65
|
**Project support files**
|
|
67
66
|
- `.claude/ukit/.ukit/` — installer manifests, metadata, backups
|
|
68
67
|
- `.ukit/` — hidden shared runtime storage for config, cache, and cross-agent memory
|
|
69
|
-
- `docs/` — PROJECT / MEMORY /
|
|
68
|
+
- `docs/` — PROJECT / MEMORY / AI_HANDOFF / WORKLOG baseline
|
|
70
69
|
|
|
71
70
|
## UKit v1.3.1 Runtime
|
|
72
71
|
|
|
@@ -91,8 +90,7 @@ UKit v1.3.1 keeps the same shared runtime contract while adding Safe Patch Proto
|
|
|
91
90
|
- install globally with `npm install -g @ngockhoale/ukit`
|
|
92
91
|
- keep using the exact same human workflow inside projects: `ukit install`
|
|
93
92
|
- preserve the same `ukit` binary, hooks, and install-first orchestration while standardizing the runtime root as hidden `.ukit/`
|
|
94
|
-
- install `docs/
|
|
95
|
-
- install gitignored `docs/TASKS.md` as a local AI task queue for deferred/ready work, with safe AI cleanup rules that remove duplicates and prune completed history without deleting unfinished human intent
|
|
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
|
|
96
94
|
- auto-route open-ended “what next?” / “continue” prompts to the `next-step` skill with a visible freshness cue when status may be stale
|
|
97
95
|
- auto-route explicit handoff/wrap-up requests to the `update-status` skill while skipping trivial/no-state-change tasks
|
|
98
96
|
- keep concrete debug/implementation/review prompts primary, so project status never replaces source/index-first task work
|
|
@@ -143,10 +143,10 @@ items:
|
|
|
143
143
|
packs:
|
|
144
144
|
- core
|
|
145
145
|
|
|
146
|
-
- id: docs-
|
|
146
|
+
- id: docs-ai-handoff
|
|
147
147
|
type: config
|
|
148
|
-
sourceTemplate: docs/
|
|
149
|
-
targetPath: docs/
|
|
148
|
+
sourceTemplate: docs/AI_HANDOFF
|
|
149
|
+
targetPath: docs/AI_HANDOFF
|
|
150
150
|
requires:
|
|
151
151
|
- docs-project
|
|
152
152
|
- docs-memory
|
|
@@ -159,19 +159,6 @@ items:
|
|
|
159
159
|
packs:
|
|
160
160
|
- core
|
|
161
161
|
|
|
162
|
-
- id: docs-tasks
|
|
163
|
-
type: config
|
|
164
|
-
sourceTemplate: docs/TASKS.md
|
|
165
|
-
targetPath: docs/TASKS.md
|
|
166
|
-
requires:
|
|
167
|
-
- docs-status
|
|
168
|
-
mergeStrategy: skip
|
|
169
|
-
variables:
|
|
170
|
-
- project.name
|
|
171
|
-
enabledByDefault: true
|
|
172
|
-
packs:
|
|
173
|
-
- core
|
|
174
|
-
|
|
175
162
|
- id: docs-bugfix
|
|
176
163
|
type: config
|
|
177
164
|
sourceTemplate: docs/BUGFIX.md
|
|
@@ -495,8 +482,7 @@ items:
|
|
|
495
482
|
targetPath: .claude/skills/next-step/SKILL.md
|
|
496
483
|
requires:
|
|
497
484
|
- docs-quality-skill
|
|
498
|
-
- docs-
|
|
499
|
-
- docs-tasks
|
|
485
|
+
- docs-ai-handoff
|
|
500
486
|
mergeStrategy: overwrite_with_backup
|
|
501
487
|
variables: []
|
|
502
488
|
enabledByDefault: true
|
|
@@ -509,8 +495,7 @@ items:
|
|
|
509
495
|
targetPath: .claude/skills/update-status/SKILL.md
|
|
510
496
|
requires:
|
|
511
497
|
- docs-quality-skill
|
|
512
|
-
- docs-
|
|
513
|
-
- docs-tasks
|
|
498
|
+
- docs-ai-handoff
|
|
514
499
|
mergeStrategy: overwrite_with_backup
|
|
515
500
|
variables: []
|
|
516
501
|
enabledByDefault: true
|
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,9 +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
|
-
|
|
66
|
-
docsTasksExists: await pathExists(path.join(projectRoot, 'docs', 'TASKS.md')),
|
|
75
|
+
docsAiHandoffExists: await pathExists(path.join(projectRoot, 'docs', 'AI_HANDOFF', 'ACTIVE.md')),
|
|
67
76
|
docsWorklogExists: await pathExists(path.join(projectRoot, 'docs', 'WORKLOG.md')),
|
|
77
|
+
docsWorklogUnderBudget: worklogLineCount <= WORKLOG_MAX_LINES,
|
|
68
78
|
allProvidersConfigured: providers.allSupported,
|
|
69
79
|
...(codexAdapterTracked
|
|
70
80
|
? {
|
|
@@ -105,9 +115,9 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
105
115
|
console.log(`[UKit] ${ok(checks.sessionMemoryDirExists)} .ukit/storage/memory/sessions/`);
|
|
106
116
|
console.log(`[UKit] ${ok(checks.docsProjectExists)} docs/PROJECT.md`);
|
|
107
117
|
console.log(`[UKit] ${ok(checks.docsMemoryExists)} docs/MEMORY.md`);
|
|
108
|
-
console.log(`[UKit] ${ok(checks.
|
|
109
|
-
console.log(`[UKit] ${ok(checks.docsTasksExists)} docs/TASKS.md`);
|
|
118
|
+
console.log(`[UKit] ${ok(checks.docsAiHandoffExists)} docs/AI_HANDOFF/`);
|
|
110
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)`);
|
|
111
121
|
if (codexAdapterTracked) {
|
|
112
122
|
console.log(`[UKit] ${ok(checks.codexReadmeExists)} .codex/README.md`);
|
|
113
123
|
console.log(`[UKit] ${ok(checks.codexSettingsExists)} .codex/settings.json`);
|
|
@@ -239,10 +239,10 @@ export async function runInstall({ packageRoot, projectRoot, packageVersion, arg
|
|
|
239
239
|
const docsLabels = [
|
|
240
240
|
'docs/PROJECT.md',
|
|
241
241
|
'docs/MEMORY.md',
|
|
242
|
-
'docs/
|
|
243
|
-
'docs/TASKS.md',
|
|
242
|
+
'docs/AI_HANDOFF/ACTIVE.md',
|
|
244
243
|
'docs/WORKLOG.md',
|
|
245
244
|
];
|
|
245
|
+
|
|
246
246
|
const docsPaths = docsLabels.map((label) => path.join(projectRoot, ...label.split('/')));
|
|
247
247
|
const missingDocs = [];
|
|
248
248
|
for (let i = 0; i < docsPaths.length; i++) {
|
|
@@ -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/
|
|
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/
|
|
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,14 +253,18 @@ export function buildRouteSummary({
|
|
|
243
253
|
),
|
|
244
254
|
);
|
|
245
255
|
const nextActionCommand = compactHelperLane ? null : nextAction?.command ?? null;
|
|
256
|
+
const handoffFile = routingContext.intentMode === 'handoff' ? 'docs/AI_HANDOFF/ACTIVE.md' : null;
|
|
246
257
|
const summaryLine = [
|
|
247
258
|
routingContext.taskType ? `task=${routingContext.taskType}` : null,
|
|
259
|
+
handoffFile ? `handoff=${handoffFile}` : null,
|
|
248
260
|
formatCompactSegment('targets', primaryTargets),
|
|
249
261
|
formatCompactSegment('tests', relatedTests),
|
|
250
262
|
formatCompactSegment('styles', styleFiles),
|
|
251
263
|
editGuardHint ? `editGuard=${editGuardHint}` : null,
|
|
252
264
|
delegationRecommendation?.hint ? `delegate=${delegationRecommendation.hint}` : null,
|
|
253
265
|
policyMode ? `policy=${policyMode}` : null,
|
|
266
|
+
handoffBudget?.warning ? `budget=${handoffBudget.warning}` : null,
|
|
267
|
+
worklogBudget?.warning ? `budget=${worklogBudget.warning}` : null,
|
|
254
268
|
].filter(Boolean).join(' | ');
|
|
255
269
|
|
|
256
270
|
return {
|
|
@@ -267,6 +281,9 @@ export function buildRouteSummary({
|
|
|
267
281
|
completionState,
|
|
268
282
|
continuationState,
|
|
269
283
|
intentMode: routingContext.intentMode ?? null,
|
|
284
|
+
handoffFile,
|
|
285
|
+
handoffBudget,
|
|
286
|
+
worklogBudget,
|
|
270
287
|
delegateHint: delegationRecommendation?.hint ?? null,
|
|
271
288
|
nextActionType: nextAction?.type ?? null,
|
|
272
289
|
nextActionCommand,
|
|
@@ -783,11 +800,11 @@ async function selectActiveSkills({ rootDir, promptText, commandText, targetFile
|
|
|
783
800
|
}
|
|
784
801
|
|
|
785
802
|
function shouldKeepRouteEntryForIntent(entry, intentMode) {
|
|
786
|
-
if (entry.id === 'next-step' && ['scoped-advice', 'docs-specific'].includes(intentMode)) {
|
|
803
|
+
if (entry.id === 'next-step' && ['scoped-advice', 'docs-specific', 'handoff'].includes(intentMode)) {
|
|
787
804
|
return false;
|
|
788
805
|
}
|
|
789
806
|
|
|
790
|
-
if (entry.id === 'update-status' &&
|
|
807
|
+
if (entry.id === 'update-status' && ['docs-specific', 'handoff'].includes(intentMode)) {
|
|
791
808
|
return false;
|
|
792
809
|
}
|
|
793
810
|
|
|
@@ -896,6 +913,10 @@ function deriveIntentMode({ promptText = '', commandText = '', targetFile = null
|
|
|
896
913
|
const openEndedStatus = hasOpenEndedStatusSignal(lower, raw) || taskQueueNext;
|
|
897
914
|
const concreteTask = hasConcreteTaskSignal(lower, raw, targetFile, { taskQueueNext });
|
|
898
915
|
|
|
916
|
+
if (hasHandoffSignal(lower, raw)) {
|
|
917
|
+
return 'handoff';
|
|
918
|
+
}
|
|
919
|
+
|
|
899
920
|
if (docsSpecific) {
|
|
900
921
|
return 'docs-specific';
|
|
901
922
|
}
|
|
@@ -927,6 +948,20 @@ function deriveIntentMode({ promptText = '', commandText = '', targetFile = null
|
|
|
927
948
|
return null;
|
|
928
949
|
}
|
|
929
950
|
|
|
951
|
+
function hasHandoffSignal(lower, raw) {
|
|
952
|
+
return /\bukit:handoff\b/.test(raw)
|
|
953
|
+
|| /\b(ai handoff|handoff phase|handoff mode|clear handoff|update handoff|start handoff|handoff clear)\b/.test(lower)
|
|
954
|
+
|| /\b(brainstorm|idea dump|ideas?).{0,80}\b(handoff|tasks?|taskify|split|breakdown)\b/.test(lower)
|
|
955
|
+
|| /\b(handoff|tasks?|taskify|split|breakdown).{0,80}\b(brainstorm|idea dump|ideas?)\b/.test(lower)
|
|
956
|
+
|| /\b(gom|chia|tach|tách|lên|len|xóa|clear|dọn).{0,80}\b(idea|ý tưởng|y tuong|task|công việc|cong viec|handoff)\b/.test(raw)
|
|
957
|
+
|| /\b(bàn giao|ban giao).{0,80}\b(ai|task|công việc|cong viec)\b/.test(raw);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function hasHandoffClearSignal(lower, raw) {
|
|
961
|
+
return /\b(clear handoff|handoff clear|dọn handoff|xóa handoff|reset handoff|compact handoff)\b/.test(lower)
|
|
962
|
+
|| (/\bukit:handoff\b/.test(raw) && /\b(clear|dọn|xóa|reset|compact)\b/.test(lower));
|
|
963
|
+
}
|
|
964
|
+
|
|
930
965
|
function hasStatusUpdateSignal(lower, raw) {
|
|
931
966
|
return /\b(update|refresh|write|sync|record|capture|summarize|summarise).{0,64}\b(status\.md|project status|current state|next candidates|session state)\b/.test(lower)
|
|
932
967
|
|| /\b(status\.md|project status).{0,64}\b(update|refresh|write|sync|record|capture|summarize|summarise)\b/.test(lower)
|
|
@@ -1459,6 +1494,108 @@ function unique(values) {
|
|
|
1459
1494
|
return [...new Set(values.filter(Boolean))];
|
|
1460
1495
|
}
|
|
1461
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
|
+
|
|
1462
1599
|
const DELEGATABLE_IMPLEMENTATION_SKILL_IDS = new Set([
|
|
1463
1600
|
'delivery',
|
|
1464
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,11 +44,12 @@ 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/
|
|
48
|
-
2. `docs/
|
|
49
|
-
3. `docs/
|
|
50
|
-
4. `docs/
|
|
51
|
-
5.
|
|
47
|
+
1. `docs/AI_HANDOFF/ACTIVE.md` first when the prompt explicitly names handoff / `ukit:handoff` / brainstorm-to-task flow
|
|
48
|
+
2. `docs/STATUS.md` (or existing root `STATUS.md` fallback) for normal status/continue prompts
|
|
49
|
+
3. `docs/TASKS.md` only for queued-task prompts, “continue” with no active status, or when status points at queued work
|
|
50
|
+
4. `docs/CODE_MAP.md` only when navigation is needed
|
|
51
|
+
5. `docs/MEMORY.md` only when constraints/decisions affect the suggestion
|
|
52
|
+
6. routed index/tree summary only if status/tasks are stale, missing, or contradicted
|
|
52
53
|
|
|
53
54
|
## Output Shape
|
|
54
55
|
|
|
@@ -17,6 +17,11 @@ function getFilePath(payload = {}) {
|
|
|
17
17
|
return payload.tool_input?.file_path || payload.file_path || '';
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function isHandoffAuthoringFile(relativePath) {
|
|
21
|
+
return relativePath === 'docs/AI_HANDOFF.md'
|
|
22
|
+
|| relativePath.startsWith('docs/AI_HANDOFF/');
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
async function listManifestPaths(backupsRoot) {
|
|
21
26
|
const dates = await fs.readdir(backupsRoot, { withFileTypes: true }).catch(() => []);
|
|
22
27
|
return dates
|
|
@@ -152,7 +157,8 @@ export async function verifyPostEdit({ projectRoot = process.cwd(), payload = {}
|
|
|
152
157
|
|
|
153
158
|
const overChangedLines = delta.changedLines > Number(config.deltaMaxChangedLines || 120);
|
|
154
159
|
const overHunks = delta.hunkCount > Number(config.deltaMaxHunks || 3);
|
|
155
|
-
const
|
|
160
|
+
const handoffAdvisory = isHandoffAuthoringFile(resolved.relative) && risk.labels.every((label) => ['large-file', 'multilingual-text'].includes(label));
|
|
161
|
+
const status = risk.strict && (overChangedLines || overHunks) ? (handoffAdvisory ? 'advisory' : 'blocked') : 'ok';
|
|
156
162
|
const postEntry = {
|
|
157
163
|
event: 'post-edit',
|
|
158
164
|
file: resolved.relative,
|
|
@@ -169,8 +175,8 @@ export async function verifyPostEdit({ projectRoot = process.cwd(), payload = {}
|
|
|
169
175
|
|
|
170
176
|
return {
|
|
171
177
|
...postEntry,
|
|
172
|
-
message: status === 'blocked'
|
|
173
|
-
?
|
|
178
|
+
message: status === 'blocked' || status === 'advisory'
|
|
179
|
+
? `${status === 'advisory' ? 'ADVISORY' : 'BLOCKED'}: '${resolved.relative}' exceeded Safe Patch delta budget (changedLines=${delta.changedLines}/${config.deltaMaxChangedLines}, hunks=${delta.hunkCount}/${config.deltaMaxHunks}). Review diff or restore from ${latest.entry.rollbackPath}.`
|
|
174
180
|
: `OK: '${resolved.relative}' delta changedLines=${delta.changedLines}, hunks=${delta.hunkCount}.`,
|
|
175
181
|
};
|
|
176
182
|
}
|
|
@@ -193,6 +199,9 @@ async function main() {
|
|
|
193
199
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
194
200
|
} else if (result.status === 'ok') {
|
|
195
201
|
process.stdout.write(`[ukit-safe-patch] ${result.message}\n`);
|
|
202
|
+
} else if (result.status === 'advisory') {
|
|
203
|
+
process.stderr.write(`[ukit-safe-patch] ${result.message}\n`);
|
|
204
|
+
process.stderr.write('[ukit-safe-patch] handoff authoring advisory — change is already written; continue updating docs/AI_HANDOFF/.\n');
|
|
196
205
|
}
|
|
197
206
|
if (result.status === 'blocked') {
|
|
198
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'),
|
|
@@ -723,11 +724,11 @@ async function selectActiveSkills({ rootDir, promptText, commandText, targetFile
|
|
|
723
724
|
}
|
|
724
725
|
|
|
725
726
|
function shouldKeepRouteEntryForIntent(entry, intentMode) {
|
|
726
|
-
if (entry.id === 'next-step' && ['scoped-advice', 'docs-specific'].includes(intentMode)) {
|
|
727
|
+
if (entry.id === 'next-step' && ['scoped-advice', 'docs-specific', 'handoff'].includes(intentMode)) {
|
|
727
728
|
return false;
|
|
728
729
|
}
|
|
729
730
|
|
|
730
|
-
if (entry.id === 'update-status' &&
|
|
731
|
+
if (entry.id === 'update-status' && ['docs-specific', 'handoff'].includes(intentMode)) {
|
|
731
732
|
return false;
|
|
732
733
|
}
|
|
733
734
|
|
|
@@ -831,6 +832,10 @@ 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
|
|
|
835
|
+
if (hasHandoffSignal(lower, raw)) {
|
|
836
|
+
return 'handoff';
|
|
837
|
+
}
|
|
838
|
+
|
|
834
839
|
if (docsSpecific) {
|
|
835
840
|
return 'docs-specific';
|
|
836
841
|
}
|
|
@@ -870,6 +875,20 @@ function hasStatusUpdateSignal(lower, raw) {
|
|
|
870
875
|
|| /\b(cập nhật|ghi lại|tổng kết|chốt session|bàn giao).{0,64}\b(status|trạng thái|việc tiếp theo)\b/.test(raw);
|
|
871
876
|
}
|
|
872
877
|
|
|
878
|
+
function hasHandoffSignal(lower, raw) {
|
|
879
|
+
return /\bukit:handoff\b/.test(raw)
|
|
880
|
+
|| /\b(ai handoff|handoff phase|handoff mode|clear handoff|update handoff|start handoff|handoff clear)\b/.test(lower)
|
|
881
|
+
|| /\b(brainstorm|idea dump|ideas?).{0,80}\b(handoff|tasks?|taskify|split|breakdown)\b/.test(lower)
|
|
882
|
+
|| /\b(handoff|tasks?|taskify|split|breakdown).{0,80}\b(brainstorm|idea dump|ideas?)\b/.test(lower)
|
|
883
|
+
|| /\b(gom|chia|tach|tách|lên|len|xóa|clear|dọn).{0,80}\b(idea|ý tưởng|y tuong|task|công việc|cong viec|handoff)\b/.test(raw)
|
|
884
|
+
|| /\b(bàn giao|ban giao).{0,80}\b(ai|task|công việc|cong viec)\b/.test(raw);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function hasHandoffClearSignal(lower, raw) {
|
|
888
|
+
return /\b(clear handoff|handoff clear|dọn handoff|xóa handoff|reset handoff|compact handoff)\b/.test(lower)
|
|
889
|
+
|| (/\bukit:handoff\b/.test(raw) && /\b(clear|dọn|xóa|reset|compact)\b/.test(lower));
|
|
890
|
+
}
|
|
891
|
+
|
|
873
892
|
function hasOpenEndedStatusSignal(lower, raw) {
|
|
874
893
|
return /\b(what next|what is next|what's next|next step|next steps|project status|current status|where are we|continue|continue from last session|roadmap|status\.md|task queue|tasks\.md|next queued task|pick next task|work from tasks)\b/.test(lower)
|
|
875
894
|
|| /\b(lam gi tiep|buoc tiep theo|tiep theo lam gi|lam tiep|dang o dau|trang thai project|tinh trang project|task tiep theo|viec tiep theo trong tasks)\b/.test(lower)
|
|
@@ -1332,8 +1351,10 @@ function buildRouteSummary({
|
|
|
1332
1351
|
),
|
|
1333
1352
|
);
|
|
1334
1353
|
const nextActionCommand = compactHelperLane ? null : nextAction?.command ?? null;
|
|
1354
|
+
const handoffFile = routingContext.intentMode === 'handoff' ? 'docs/AI_HANDOFF/ACTIVE.md' : null;
|
|
1335
1355
|
const line = [
|
|
1336
1356
|
routingContext.taskType ? `task=${routingContext.taskType}` : null,
|
|
1357
|
+
handoffFile ? `handoff=${handoffFile}` : null,
|
|
1337
1358
|
formatCompactSegment('targets', primaryTargets),
|
|
1338
1359
|
formatCompactSegment('tests', relatedTests),
|
|
1339
1360
|
formatCompactSegment('styles', styleFiles),
|
|
@@ -1356,6 +1377,7 @@ function buildRouteSummary({
|
|
|
1356
1377
|
completionState,
|
|
1357
1378
|
continuationState,
|
|
1358
1379
|
intentMode: routingContext.intentMode ?? null,
|
|
1380
|
+
handoffFile,
|
|
1359
1381
|
delegateHint: delegationRecommendation?.hint ?? null,
|
|
1360
1382
|
nextActionType: nextAction?.type ?? null,
|
|
1361
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,8 +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/
|
|
51
|
-
- `docs/TASKS.md`
|
|
50
|
+
- `docs/AI_HANDOFF/`
|
|
52
51
|
- `docs/WORKLOG.md`
|
|
53
52
|
|
|
54
53
|
### 4) Open your AI tool
|
|
@@ -98,8 +97,7 @@ ukit install
|
|
|
98
97
|
Check that the docs baseline files exist and are filled in:
|
|
99
98
|
- `docs/PROJECT.md`
|
|
100
99
|
- `docs/MEMORY.md`
|
|
101
|
-
- `docs/
|
|
102
|
-
- `docs/TASKS.md`
|
|
100
|
+
- `docs/AI_HANDOFF/`
|
|
103
101
|
- `docs/WORKLOG.md`
|
|
104
102
|
|
|
105
103
|
---
|
|
@@ -44,9 +44,8 @@
|
|
|
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/
|
|
48
|
-
4. Read `docs/
|
|
49
|
-
5.
|
|
50
|
-
6.
|
|
51
|
-
7.
|
|
52
|
-
8. Verify understanding against source before acting — **docs orient, source is truth; keep the index-first workflow intact**
|
|
47
|
+
3. Read `docs/AI_HANDOFF/ACTIVE.md` when continuing cross-AI planning, task breakdown, or task implementation handoff work
|
|
48
|
+
4. Read `docs/CODE_MAP.md` if it exists — structural navigation index
|
|
49
|
+
5. Use the installed source-code index / routed helpers to localize the smallest relevant file + test set first
|
|
50
|
+
6. Scan recent `docs/WORKLOG.md` entries if continuing prior work
|
|
51
|
+
7. Verify understanding against source before acting — **docs orient, source is truth; keep the index-first workflow intact**
|
|
@@ -138,11 +138,10 @@ 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/
|
|
142
|
-
3.
|
|
141
|
+
2. read `docs/AI_HANDOFF/ACTIVE.md` when the team is passing planning, task breakdown, or implementation context between AIs
|
|
142
|
+
3. verify the handoff against source/index before treating it as authoritative
|
|
143
143
|
4. suggest only a few actionable next candidates
|
|
144
|
-
5. if
|
|
145
|
-
6. if the prompt names a concrete bug/feature/review target, keep the concrete workflow primary instead of producing a global roadmap
|
|
144
|
+
5. if the prompt names a concrete bug/feature/review target, keep the concrete workflow primary instead of producing a global roadmap
|
|
146
145
|
|
|
147
146
|
---
|
|
148
147
|
|
|
@@ -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
|