@ngockhoale/ukit 1.5.0 → 1.5.2
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 +24 -0
- package/README.md +3 -5
- package/manifests/platform.full.yaml +25 -22
- package/package.json +1 -1
- package/src/cli/adapters.js +4 -0
- package/src/cli/commands/doctor.js +2 -4
- package/src/cli/commands/install.js +3 -3
- package/src/cli/commands/uninstall.js +1 -1
- package/src/core/applyPlan.js +49 -8
- package/src/core/ensureGitignore.js +0 -2
- package/src/core/runtimeConfig.js +1 -1
- package/src/index/taskRouting.js +23 -2
- package/src/manifest/selectItems.js +2 -0
- package/templates/.claude/agents/ukit-small-task-maintainer.md +1 -1
- package/templates/.claude/skills/next-step/SKILL.md +6 -5
- package/templates/.claude/ukit/index/post-edit-verify.mjs +11 -3
- package/templates/.claude/ukit/index/route-task.mjs +20 -2
- package/templates/.codex/README.md +111 -39
- package/templates/AGENTS.md +56 -17
- package/templates/CLAUDE.md +41 -15
- package/templates/adapter-presets/opencode/opencode.template.json +1 -1
- package/templates/docs/AI_HANDOFF.md +118 -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/ukit/storage/config.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 1.5.2 - 2026-05-28
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- 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 `docs/AI_HANDOFF.md` automatically.
|
|
11
|
+
- Added a reusable generic `docs/AI_HANDOFF.md` handoff template for installed projects.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Handoff prompts now route through `docs-quality` skill instead of generic `update-status`, making `docs/AI_HANDOFF.md` the primary coordination artifact.
|
|
16
|
+
- Updated installed `next-step` skill guidance to read `docs/AI_HANDOFF.md` first for explicit handoff prompts.
|
|
17
|
+
- Updated `route-task` mirror and hook behavior in both source and installed artifact so `ukit install` users get consistent handoff routing.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Handoff authoring is now advisory (exit 0) in Safe Patch, so large `docs/AI_HANDOFF.md` batches no longer block the handoff workflow.
|
|
22
|
+
- Hard runtime/shared-risk file broad rewrites still block by default unless `advisoryOnly=true` or `UKIT_SAFE_PATCH_ADVISORY=1` is set.
|
|
23
|
+
|
|
24
|
+
### Tests
|
|
25
|
+
|
|
26
|
+
- Added explicit handoff routing coverage in `tests/index/taskRouting.test.js`.
|
|
27
|
+
- Verified Safe Patch protocol still passes 12 tests including the new handoff advisory case.
|
|
28
|
+
|
|
5
29
|
## 1.5.0 - 2026-05-24
|
|
6
30
|
|
|
7
31
|
### 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.md`
|
|
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.md` as the default cross-AI handoff file for plan → task breakdown → implementation continuity, with explicit sections so one AI can plan, another can refine tasks, and another can implement them reliably
|
|
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
|
|
@@ -84,7 +84,7 @@ items:
|
|
|
84
84
|
requires:
|
|
85
85
|
- docs-project
|
|
86
86
|
- docs-memory
|
|
87
|
-
mergeStrategy:
|
|
87
|
+
mergeStrategy: overwrite_with_backup
|
|
88
88
|
variables:
|
|
89
89
|
- project.name
|
|
90
90
|
- project.root
|
|
@@ -107,7 +107,7 @@ items:
|
|
|
107
107
|
requires:
|
|
108
108
|
- docs-project
|
|
109
109
|
- docs-memory
|
|
110
|
-
mergeStrategy:
|
|
110
|
+
mergeStrategy: overwrite_with_backup
|
|
111
111
|
variables:
|
|
112
112
|
- project.name
|
|
113
113
|
- project.stack
|
|
@@ -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.md
|
|
149
|
+
targetPath: docs/AI_HANDOFF.md
|
|
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
|
|
@@ -1302,6 +1287,15 @@ items:
|
|
|
1302
1287
|
packs:
|
|
1303
1288
|
- core
|
|
1304
1289
|
|
|
1290
|
+
- id: multi-antigravity-agents-link
|
|
1291
|
+
type: link
|
|
1292
|
+
sourceTemplate: .claude/agents
|
|
1293
|
+
targetPath: .antigravity/agents
|
|
1294
|
+
requires: []
|
|
1295
|
+
enabledByDefault: true
|
|
1296
|
+
packs:
|
|
1297
|
+
- core
|
|
1298
|
+
|
|
1305
1299
|
- id: multi-antigravity-rules
|
|
1306
1300
|
type: config
|
|
1307
1301
|
sourceTemplate: adapter-presets/antigravity/rules.md
|
|
@@ -1349,6 +1343,15 @@ items:
|
|
|
1349
1343
|
packs:
|
|
1350
1344
|
- core
|
|
1351
1345
|
|
|
1346
|
+
- id: multi-codex-agents-link
|
|
1347
|
+
type: link
|
|
1348
|
+
sourceTemplate: .claude/agents
|
|
1349
|
+
targetPath: .codex/agents
|
|
1350
|
+
requires: []
|
|
1351
|
+
enabledByDefault: true
|
|
1352
|
+
packs:
|
|
1353
|
+
- core
|
|
1354
|
+
|
|
1352
1355
|
- id: multi-codex-readme
|
|
1353
1356
|
type: config
|
|
1354
1357
|
sourceTemplate: .codex/README.md
|
package/package.json
CHANGED
package/src/cli/adapters.js
CHANGED
|
@@ -7,12 +7,14 @@ export const OPTIONAL_ADAPTERS = [
|
|
|
7
7
|
'multi-antigravity-ukit-link',
|
|
8
8
|
'multi-antigravity-rules',
|
|
9
9
|
'multi-antigravity-readme',
|
|
10
|
+
'multi-antigravity-agents-link',
|
|
10
11
|
],
|
|
11
12
|
managedPaths: [
|
|
12
13
|
'.antigravity/skills',
|
|
13
14
|
'.antigravity/ukit',
|
|
14
15
|
'.antigravity/rules/rules.md',
|
|
15
16
|
'.antigravity/README.md',
|
|
17
|
+
'.antigravity/agents',
|
|
16
18
|
],
|
|
17
19
|
},
|
|
18
20
|
{
|
|
@@ -24,6 +26,7 @@ export const OPTIONAL_ADAPTERS = [
|
|
|
24
26
|
'multi-codex-readme',
|
|
25
27
|
'multi-codex-settings-json',
|
|
26
28
|
'multi-codex-settings-local',
|
|
29
|
+
'multi-codex-agents-link',
|
|
27
30
|
],
|
|
28
31
|
managedPaths: [
|
|
29
32
|
'.codex/skills',
|
|
@@ -31,6 +34,7 @@ export const OPTIONAL_ADAPTERS = [
|
|
|
31
34
|
'.codex/README.md',
|
|
32
35
|
'.codex/settings.json',
|
|
33
36
|
'.codex/settings.local.json',
|
|
37
|
+
'.codex/agents',
|
|
34
38
|
],
|
|
35
39
|
},
|
|
36
40
|
{
|
|
@@ -62,8 +62,7 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
62
62
|
sessionMemoryDirExists: await pathExists(runtimePaths.sessionsDir),
|
|
63
63
|
docsProjectExists: await pathExists(path.join(projectRoot, 'docs', 'PROJECT.md')),
|
|
64
64
|
docsMemoryExists: await pathExists(path.join(projectRoot, 'docs', 'MEMORY.md')),
|
|
65
|
-
|
|
66
|
-
docsTasksExists: await pathExists(path.join(projectRoot, 'docs', 'TASKS.md')),
|
|
65
|
+
docsAiHandoffExists: await pathExists(path.join(projectRoot, 'docs', 'AI_HANDOFF.md')),
|
|
67
66
|
docsWorklogExists: await pathExists(path.join(projectRoot, 'docs', 'WORKLOG.md')),
|
|
68
67
|
allProvidersConfigured: providers.allSupported,
|
|
69
68
|
...(codexAdapterTracked
|
|
@@ -105,8 +104,7 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
105
104
|
console.log(`[UKit] ${ok(checks.sessionMemoryDirExists)} .ukit/storage/memory/sessions/`);
|
|
106
105
|
console.log(`[UKit] ${ok(checks.docsProjectExists)} docs/PROJECT.md`);
|
|
107
106
|
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`);
|
|
107
|
+
console.log(`[UKit] ${ok(checks.docsAiHandoffExists)} docs/AI_HANDOFF.md`);
|
|
110
108
|
console.log(`[UKit] ${ok(checks.docsWorklogExists)} docs/WORKLOG.md`);
|
|
111
109
|
if (codexAdapterTracked) {
|
|
112
110
|
console.log(`[UKit] ${ok(checks.codexReadmeExists)} .codex/README.md`);
|
|
@@ -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.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.md, 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.md, docs/WORKLOG.md contain user content and were preserved. Delete manually if needed.');
|
|
51
51
|
}
|
package/src/core/applyPlan.js
CHANGED
|
@@ -2,6 +2,16 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { writeFileAtomic, copyFileSafe, createDirectoryLink, removeLinkOnly } from './fileOps.js';
|
|
4
4
|
|
|
5
|
+
const TCC_PROTECTED_PREFIXES = ['.codex/', '.antigravity/'];
|
|
6
|
+
|
|
7
|
+
function isTccProtectedPath(filePath) {
|
|
8
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
9
|
+
return TCC_PROTECTED_PREFIXES.some((prefix) => {
|
|
10
|
+
const idx = normalized.indexOf(`/${prefix}`);
|
|
11
|
+
return idx !== -1 || normalized.startsWith(prefix);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
export async function applyDiffResults(diffResults, { backupRoot, projectRoot } = {}) {
|
|
6
16
|
const writes = [];
|
|
7
17
|
let skippedUpdates = 0;
|
|
@@ -12,14 +22,9 @@ export async function applyDiffResults(diffResults, { backupRoot, projectRoot }
|
|
|
12
22
|
}
|
|
13
23
|
|
|
14
24
|
if (entry.type === 'link') {
|
|
15
|
-
// Remove existing symlink before recreating.
|
|
16
|
-
// Use removeLinkOnly — NOT removeLinkOrDir — to protect real directories that may
|
|
17
|
-
// contain user content. If the path is not a symlink, skip and warn instead of
|
|
18
|
-
// recursively deleting it.
|
|
19
25
|
if (entry.action === 'update') {
|
|
20
26
|
const removed = await removeLinkOnly(entry.targetPath);
|
|
21
27
|
if (!removed) {
|
|
22
|
-
// Determine what actually exists at this path for a precise warning message.
|
|
23
28
|
let existingKind = 'unknown path';
|
|
24
29
|
try {
|
|
25
30
|
const stat = await fs.lstat(entry.targetPath);
|
|
@@ -34,7 +39,18 @@ export async function applyDiffResults(diffResults, { backupRoot, projectRoot }
|
|
|
34
39
|
continue;
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
|
-
|
|
42
|
+
try {
|
|
43
|
+
await createDirectoryLink(entry.linkTarget, entry.targetPath);
|
|
44
|
+
} catch (linkError) {
|
|
45
|
+
if (linkError.code === 'EPERM' || linkError.code === 'EACCES') {
|
|
46
|
+
console.warn(
|
|
47
|
+
`[UKit] Warning: skipping link creation for ${entry.targetPath} — permission denied. Run 'ukit install' from within the target tool to create it.`,
|
|
48
|
+
);
|
|
49
|
+
skippedUpdates += 1;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
throw linkError;
|
|
53
|
+
}
|
|
38
54
|
writes.push({
|
|
39
55
|
id: entry.id,
|
|
40
56
|
targetPath: entry.targetPath,
|
|
@@ -54,7 +70,18 @@ export async function applyDiffResults(diffResults, { backupRoot, projectRoot }
|
|
|
54
70
|
const relPath = path.relative(projectRoot, entry.targetPath);
|
|
55
71
|
const timestamp = Date.now();
|
|
56
72
|
const backupPath = path.join(backupRoot, `${relPath}.${timestamp}.bak`);
|
|
57
|
-
|
|
73
|
+
try {
|
|
74
|
+
await copyFileSafe(entry.targetPath, backupPath);
|
|
75
|
+
} catch (backupError) {
|
|
76
|
+
if (backupError.code === 'EPERM' || backupError.code === 'EACCES') {
|
|
77
|
+
console.warn(
|
|
78
|
+
`[UKit] Warning: skipping backup for ${entry.targetPath} — permission denied.`,
|
|
79
|
+
);
|
|
80
|
+
skippedUpdates += 1;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
throw backupError;
|
|
84
|
+
}
|
|
58
85
|
}
|
|
59
86
|
|
|
60
87
|
const binaryEntry = Buffer.isBuffer(entry.renderedContent);
|
|
@@ -73,7 +100,21 @@ export async function applyDiffResults(diffResults, { backupRoot, projectRoot }
|
|
|
73
100
|
)
|
|
74
101
|
: entry.renderedContent;
|
|
75
102
|
|
|
76
|
-
|
|
103
|
+
try {
|
|
104
|
+
await writeFileAtomic(entry.targetPath, nextContent);
|
|
105
|
+
} catch (writeError) {
|
|
106
|
+
if (
|
|
107
|
+
(writeError.code === 'EPERM' || writeError.code === 'EACCES') &&
|
|
108
|
+
isTccProtectedPath(entry.targetPath)
|
|
109
|
+
) {
|
|
110
|
+
console.warn(
|
|
111
|
+
`[UKit] Warning: skipping write for ${entry.targetPath} — permission denied (macOS TCC). Run 'ukit install' from within the owning tool to update it.`,
|
|
112
|
+
);
|
|
113
|
+
skippedUpdates += 1;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
throw writeError;
|
|
117
|
+
}
|
|
77
118
|
if (typeof entry.mode === 'number') {
|
|
78
119
|
await fs.chmod(entry.targetPath, entry.mode);
|
|
79
120
|
}
|
package/src/index/taskRouting.js
CHANGED
|
@@ -243,8 +243,10 @@ export function buildRouteSummary({
|
|
|
243
243
|
),
|
|
244
244
|
);
|
|
245
245
|
const nextActionCommand = compactHelperLane ? null : nextAction?.command ?? null;
|
|
246
|
+
const handoffFile = routingContext.intentMode === 'handoff' ? 'docs/AI_HANDOFF.md' : null;
|
|
246
247
|
const summaryLine = [
|
|
247
248
|
routingContext.taskType ? `task=${routingContext.taskType}` : null,
|
|
249
|
+
handoffFile ? `handoff=${handoffFile}` : null,
|
|
248
250
|
formatCompactSegment('targets', primaryTargets),
|
|
249
251
|
formatCompactSegment('tests', relatedTests),
|
|
250
252
|
formatCompactSegment('styles', styleFiles),
|
|
@@ -267,6 +269,7 @@ export function buildRouteSummary({
|
|
|
267
269
|
completionState,
|
|
268
270
|
continuationState,
|
|
269
271
|
intentMode: routingContext.intentMode ?? null,
|
|
272
|
+
handoffFile,
|
|
270
273
|
delegateHint: delegationRecommendation?.hint ?? null,
|
|
271
274
|
nextActionType: nextAction?.type ?? null,
|
|
272
275
|
nextActionCommand,
|
|
@@ -783,11 +786,11 @@ async function selectActiveSkills({ rootDir, promptText, commandText, targetFile
|
|
|
783
786
|
}
|
|
784
787
|
|
|
785
788
|
function shouldKeepRouteEntryForIntent(entry, intentMode) {
|
|
786
|
-
if (entry.id === 'next-step' && ['scoped-advice', 'docs-specific'].includes(intentMode)) {
|
|
789
|
+
if (entry.id === 'next-step' && ['scoped-advice', 'docs-specific', 'handoff'].includes(intentMode)) {
|
|
787
790
|
return false;
|
|
788
791
|
}
|
|
789
792
|
|
|
790
|
-
if (entry.id === 'update-status' &&
|
|
793
|
+
if (entry.id === 'update-status' && ['docs-specific', 'handoff'].includes(intentMode)) {
|
|
791
794
|
return false;
|
|
792
795
|
}
|
|
793
796
|
|
|
@@ -896,6 +899,10 @@ function deriveIntentMode({ promptText = '', commandText = '', targetFile = null
|
|
|
896
899
|
const openEndedStatus = hasOpenEndedStatusSignal(lower, raw) || taskQueueNext;
|
|
897
900
|
const concreteTask = hasConcreteTaskSignal(lower, raw, targetFile, { taskQueueNext });
|
|
898
901
|
|
|
902
|
+
if (hasHandoffSignal(lower, raw)) {
|
|
903
|
+
return 'handoff';
|
|
904
|
+
}
|
|
905
|
+
|
|
899
906
|
if (docsSpecific) {
|
|
900
907
|
return 'docs-specific';
|
|
901
908
|
}
|
|
@@ -927,6 +934,20 @@ function deriveIntentMode({ promptText = '', commandText = '', targetFile = null
|
|
|
927
934
|
return null;
|
|
928
935
|
}
|
|
929
936
|
|
|
937
|
+
function hasHandoffSignal(lower, raw) {
|
|
938
|
+
return /\bukit:handoff\b/.test(raw)
|
|
939
|
+
|| /\b(ai handoff|handoff phase|handoff mode|clear handoff|update handoff|start handoff|handoff clear)\b/.test(lower)
|
|
940
|
+
|| /\b(brainstorm|idea dump|ideas?).{0,80}\b(handoff|tasks?|taskify|split|breakdown)\b/.test(lower)
|
|
941
|
+
|| /\b(handoff|tasks?|taskify|split|breakdown).{0,80}\b(brainstorm|idea dump|ideas?)\b/.test(lower)
|
|
942
|
+
|| /\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)
|
|
943
|
+
|| /\b(bàn giao|ban giao).{0,80}\b(ai|task|công việc|cong viec)\b/.test(raw);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function hasHandoffClearSignal(lower, raw) {
|
|
947
|
+
return /\b(clear handoff|handoff clear|dọn handoff|xóa handoff|reset handoff|compact handoff)\b/.test(lower)
|
|
948
|
+
|| (/\bukit:handoff\b/.test(raw) && /\b(clear|dọn|xóa|reset|compact)\b/.test(lower));
|
|
949
|
+
}
|
|
950
|
+
|
|
930
951
|
function hasStatusUpdateSignal(lower, raw) {
|
|
931
952
|
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
953
|
|| /\b(status\.md|project status).{0,64}\b(update|refresh|write|sync|record|capture|summarize|summarise)\b/.test(lower)
|
|
@@ -3,11 +3,13 @@ export const OPTIONAL_ADAPTER_ITEM_IDS = new Set([
|
|
|
3
3
|
'multi-antigravity-ukit-link',
|
|
4
4
|
'multi-antigravity-rules',
|
|
5
5
|
'multi-antigravity-readme',
|
|
6
|
+
'multi-antigravity-agents-link',
|
|
6
7
|
'multi-codex-skills-link',
|
|
7
8
|
'multi-codex-ukit-link',
|
|
8
9
|
'multi-codex-readme',
|
|
9
10
|
'multi-codex-settings-json',
|
|
10
11
|
'multi-codex-settings-local',
|
|
12
|
+
'multi-codex-agents-link',
|
|
11
13
|
'multi-opencode-config',
|
|
12
14
|
]);
|
|
13
15
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ukit-small-task-maintainer
|
|
3
3
|
description: "Internal UKit maintenance subagent for low-risk, reversible UKit decisions. Use proactively when UKit needs to decide or perform safe cleanup such as pruning docs/TASKS.md, classifying queued work, choosing whether compact/summarization is appropriate, summarizing local docs/status, or maintaining small UKit runtime queues. Do not use for product implementation, security, release/publish, data-loss, architecture, or risky/shared code changes."
|
|
4
|
-
model:
|
|
4
|
+
model: unic-lite
|
|
5
5
|
color: cyan
|
|
6
6
|
tools: ["Read", "Grep", "Glob", "Edit", "Write"]
|
|
7
7
|
---
|
|
@@ -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.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,10 @@ 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
|
+
}
|
|
23
|
+
|
|
20
24
|
async function listManifestPaths(backupsRoot) {
|
|
21
25
|
const dates = await fs.readdir(backupsRoot, { withFileTypes: true }).catch(() => []);
|
|
22
26
|
return dates
|
|
@@ -152,7 +156,8 @@ export async function verifyPostEdit({ projectRoot = process.cwd(), payload = {}
|
|
|
152
156
|
|
|
153
157
|
const overChangedLines = delta.changedLines > Number(config.deltaMaxChangedLines || 120);
|
|
154
158
|
const overHunks = delta.hunkCount > Number(config.deltaMaxHunks || 3);
|
|
155
|
-
const
|
|
159
|
+
const handoffAdvisory = isHandoffAuthoringFile(resolved.relative) && risk.labels.every((label) => ['large-file', 'multilingual-text'].includes(label));
|
|
160
|
+
const status = risk.strict && (overChangedLines || overHunks) ? (handoffAdvisory ? 'advisory' : 'blocked') : 'ok';
|
|
156
161
|
const postEntry = {
|
|
157
162
|
event: 'post-edit',
|
|
158
163
|
file: resolved.relative,
|
|
@@ -169,8 +174,8 @@ export async function verifyPostEdit({ projectRoot = process.cwd(), payload = {}
|
|
|
169
174
|
|
|
170
175
|
return {
|
|
171
176
|
...postEntry,
|
|
172
|
-
message: status === 'blocked'
|
|
173
|
-
?
|
|
177
|
+
message: status === 'blocked' || status === 'advisory'
|
|
178
|
+
? `${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
179
|
: `OK: '${resolved.relative}' delta changedLines=${delta.changedLines}, hunks=${delta.hunkCount}.`,
|
|
175
180
|
};
|
|
176
181
|
}
|
|
@@ -193,6 +198,9 @@ async function main() {
|
|
|
193
198
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
194
199
|
} else if (result.status === 'ok') {
|
|
195
200
|
process.stdout.write(`[ukit-safe-patch] ${result.message}\n`);
|
|
201
|
+
} else if (result.status === 'advisory') {
|
|
202
|
+
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.md.\n');
|
|
196
204
|
}
|
|
197
205
|
if (result.status === 'blocked') {
|
|
198
206
|
const advisory = isSafePatchAdvisoryOnly(runtimeConfig);
|
|
@@ -723,11 +723,11 @@ async function selectActiveSkills({ rootDir, promptText, commandText, targetFile
|
|
|
723
723
|
}
|
|
724
724
|
|
|
725
725
|
function shouldKeepRouteEntryForIntent(entry, intentMode) {
|
|
726
|
-
if (entry.id === 'next-step' && ['scoped-advice', 'docs-specific'].includes(intentMode)) {
|
|
726
|
+
if (entry.id === 'next-step' && ['scoped-advice', 'docs-specific', 'handoff'].includes(intentMode)) {
|
|
727
727
|
return false;
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
-
if (entry.id === 'update-status' &&
|
|
730
|
+
if (entry.id === 'update-status' && ['docs-specific', 'handoff'].includes(intentMode)) {
|
|
731
731
|
return false;
|
|
732
732
|
}
|
|
733
733
|
|
|
@@ -835,6 +835,10 @@ function deriveIntentMode({ promptText = '', commandText = '', targetFile = null
|
|
|
835
835
|
return 'docs-specific';
|
|
836
836
|
}
|
|
837
837
|
|
|
838
|
+
if (hasHandoffSignal(lower, raw)) {
|
|
839
|
+
return 'handoff';
|
|
840
|
+
}
|
|
841
|
+
|
|
838
842
|
if (statusUpdate) {
|
|
839
843
|
return 'status-update';
|
|
840
844
|
}
|
|
@@ -870,6 +874,20 @@ function hasStatusUpdateSignal(lower, raw) {
|
|
|
870
874
|
|| /\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
875
|
}
|
|
872
876
|
|
|
877
|
+
function hasHandoffSignal(lower, raw) {
|
|
878
|
+
return /\bukit:handoff\b/.test(raw)
|
|
879
|
+
|| /\b(ai handoff|handoff phase|handoff mode|clear handoff|update handoff|start handoff|handoff clear)\b/.test(lower)
|
|
880
|
+
|| /\b(brainstorm|idea dump|ideas?).{0,80}\b(handoff|tasks?|taskify|split|breakdown)\b/.test(lower)
|
|
881
|
+
|| /\b(handoff|tasks?|taskify|split|breakdown).{0,80}\b(brainstorm|idea dump|ideas?)\b/.test(lower)
|
|
882
|
+
|| /\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)
|
|
883
|
+
|| /\b(bàn giao|ban giao).{0,80}\b(ai|task|công việc|cong viec)\b/.test(raw);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function hasHandoffClearSignal(lower, raw) {
|
|
887
|
+
return /\b(clear handoff|handoff clear|dọn handoff|xóa handoff|reset handoff|compact handoff)\b/.test(lower)
|
|
888
|
+
|| (/\bukit:handoff\b/.test(raw) && /\b(clear|dọn|xóa|reset|compact)\b/.test(lower));
|
|
889
|
+
}
|
|
890
|
+
|
|
873
891
|
function hasOpenEndedStatusSignal(lower, raw) {
|
|
874
892
|
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
893
|
|| /\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)
|