@ngockhoale/ukit 2.7.13 → 3.0.0
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 +13 -0
- package/manifests/documentation.yaml +11 -0
- package/manifests/platform.full.yaml +182 -0
- package/manifests/platform.user.yaml +53 -0
- package/package.json +3 -1
- package/src/cli/commands/diff.js +4 -2
- package/src/cli/commands/doctor.js +22 -1
- package/src/cli/commands/install.js +10 -0
- package/src/cli/commands/memory.js +142 -3
- package/src/cli/commands/playbook.js +53 -0
- package/src/cli/index.js +7 -0
- package/src/core/memory/recordStore.js +81 -0
- package/src/core/memory/storeV2.js +16 -52
- package/src/core/memory/userMemory.js +111 -0
- package/src/core/paths.js +1 -0
- package/src/core/runInstallPipeline.js +96 -3
- package/src/core/runtimeConfig.js +170 -5
- package/src/core/userPaths.js +21 -0
- package/src/core/userPlaybooks.js +185 -0
- package/src/index/taskRouting.js +422 -21
- package/src/index/verificationPlan.js +17 -0
- package/src/manifest/validateManifest.js +19 -0
- package/templates/.claude/config/providers.md +1 -3
- package/templates/.claude/skills/principle-attack-the-premise/SKILL.md +16 -0
- package/templates/.claude/skills/principle-boundary-discipline/SKILL.md +16 -0
- package/templates/.claude/skills/principle-encode-lessons-in-structure/SKILL.md +16 -0
- package/templates/.claude/skills/principle-fix-root-causes/SKILL.md +18 -0
- package/templates/.claude/skills/principle-foundational-thinking/SKILL.md +17 -0
- package/templates/.claude/skills/principle-guard-the-context-window/SKILL.md +16 -0
- package/templates/.claude/skills/principle-laziness-protocol/SKILL.md +17 -0
- package/templates/.claude/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +16 -0
- package/templates/.claude/skills/principle-minimize-reader-load/SKILL.md +17 -0
- package/templates/.claude/skills/principle-model-the-domain/SKILL.md +16 -0
- package/templates/.claude/skills/principle-never-block-on-the-human/SKILL.md +16 -0
- package/templates/.claude/skills/principle-prove-it-works/SKILL.md +18 -0
- package/templates/.claude/skills/principle-sequence-verifiable-units/SKILL.md +16 -0
- package/templates/.claude/skills/principle-subtract-before-you-add/SKILL.md +16 -0
- package/templates/.claude/skills/principle-test-behavior-not-implementation/SKILL.md +18 -0
- package/templates/.claude/ukit/index/route-task.mjs +652 -28
- package/templates/.claude/ukit/runtime/execution-ledger.mjs +238 -9
- package/templates/ukit/README.md +31 -0
- package/templates/ukit/storage/config.json +10 -0
- package/templates/user/README.md +21 -0
- package/templates/user/playbooks/bug-fix.md +18 -0
- package/templates/user/playbooks/issue-implementation.md +14 -0
- package/templates/user/storage/config.json +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
## 3.0.0 - 2026-09-23
|
|
7
|
+
|
|
8
|
+
- Same content as 2.8.0 — re-versioned as a major: the UserLayer/ProjectLayer split plus the new `templates/user/` template set is a breaking-scale change and belongs on 3.x. 2.8.0 is deprecated on npm.
|
|
9
|
+
|
|
10
|
+
## 2.8.0 - 2026-09-23
|
|
11
|
+
|
|
12
|
+
- **User-Level `.ukit` layer (cycle C46)**: new `~/.ukit/` layer resolved via `os.homedir()` (correct on Windows/Linux/macOS), immutable on `ukit update` (`level: "user"` manifests + `mergeStrategy: skip`).
|
|
13
|
+
- Canonical API: `src/core/userPaths.js`, `src/core/memory/{recordStore,userMemory}.js`, `src/core/userPlaybooks.js`.
|
|
14
|
+
- Merge-per-key precedence: project wins config keys, user wins personal data; `loadRuntimeConfig(projectRoot, {homeDir})` deep-merges defaults←user←project; invalid user-only config warns and falls back to defaults.
|
|
15
|
+
- Install seeds the user layer (`manifests/platform.user.yaml` + `templates/user/`); router + `route-task.mjs` mirror are user-layer aware; new `ukit playbook` CLI; `ukit memory --user`; doctor user-layer section.
|
|
16
|
+
- **v3 pstack-derived specs**: playbook policies, principle index (15 leaf skills), `modelRoles`, typed verdicts + patch-id currency, decision log (`docs/pstack/SPEC-*.md`, `V3_RESHAPE.md`).
|
|
17
|
+
- **M01.1**: additive `ResolvedTaskRoute` v1 behind `routing.routeSchema.stage` (default `off` = byte-identical legacy route).
|
|
18
|
+
|
|
6
19
|
## 2.7.13 - 2026-09-22
|
|
7
20
|
|
|
8
21
|
- Same content as 2.7.12 (npm staged-publish never completed; re-published as 2.7.13).
|
|
@@ -330,6 +330,17 @@ entries:
|
|
|
330
330
|
validation: [manual]
|
|
331
331
|
archive_policy: never
|
|
332
332
|
notes: dir entry — covers all files beneath (handoff lane reports, perf findings)
|
|
333
|
+
- id: docs-pstack
|
|
334
|
+
path: docs/pstack/
|
|
335
|
+
class: canonical
|
|
336
|
+
audience: [agent, maintainer]
|
|
337
|
+
owner: product
|
|
338
|
+
source_of_truth: docs/pstack/
|
|
339
|
+
merge_strategy: none
|
|
340
|
+
load_policy: task-routed
|
|
341
|
+
validation: [manual]
|
|
342
|
+
archive_policy: never
|
|
343
|
+
notes: dir entry — pstack→UKit v3 integration corpus, specs, and roadmaps
|
|
333
344
|
- id: docs-archive
|
|
334
345
|
path: docs/archive/
|
|
335
346
|
class: archive
|
|
@@ -845,6 +845,188 @@ items:
|
|
|
845
845
|
packs:
|
|
846
846
|
- core
|
|
847
847
|
|
|
848
|
+
# SPEC-principle-index §2.2: 15 principle leaf skills — reference material only
|
|
849
|
+
# (disable-model-invocation), lazy-loaded by name from the emitted principle index.
|
|
850
|
+
- id: principle-laziness-protocol-skill
|
|
851
|
+
type: skill
|
|
852
|
+
sourceTemplate: .claude/skills/principle-laziness-protocol/SKILL.md
|
|
853
|
+
targetPath: .claude/skills/principle-laziness-protocol/SKILL.md
|
|
854
|
+
requires:
|
|
855
|
+
- core-skill-delivery
|
|
856
|
+
mergeStrategy: overwrite_with_backup
|
|
857
|
+
variables: []
|
|
858
|
+
enabledByDefault: true
|
|
859
|
+
packs:
|
|
860
|
+
- core
|
|
861
|
+
|
|
862
|
+
- id: principle-foundational-thinking-skill
|
|
863
|
+
type: skill
|
|
864
|
+
sourceTemplate: .claude/skills/principle-foundational-thinking/SKILL.md
|
|
865
|
+
targetPath: .claude/skills/principle-foundational-thinking/SKILL.md
|
|
866
|
+
requires:
|
|
867
|
+
- core-skill-delivery
|
|
868
|
+
mergeStrategy: overwrite_with_backup
|
|
869
|
+
variables: []
|
|
870
|
+
enabledByDefault: true
|
|
871
|
+
packs:
|
|
872
|
+
- core
|
|
873
|
+
|
|
874
|
+
- id: principle-subtract-before-you-add-skill
|
|
875
|
+
type: skill
|
|
876
|
+
sourceTemplate: .claude/skills/principle-subtract-before-you-add/SKILL.md
|
|
877
|
+
targetPath: .claude/skills/principle-subtract-before-you-add/SKILL.md
|
|
878
|
+
requires:
|
|
879
|
+
- core-skill-delivery
|
|
880
|
+
mergeStrategy: overwrite_with_backup
|
|
881
|
+
variables: []
|
|
882
|
+
enabledByDefault: true
|
|
883
|
+
packs:
|
|
884
|
+
- core
|
|
885
|
+
|
|
886
|
+
- id: principle-minimize-reader-load-skill
|
|
887
|
+
type: skill
|
|
888
|
+
sourceTemplate: .claude/skills/principle-minimize-reader-load/SKILL.md
|
|
889
|
+
targetPath: .claude/skills/principle-minimize-reader-load/SKILL.md
|
|
890
|
+
requires:
|
|
891
|
+
- core-skill-delivery
|
|
892
|
+
mergeStrategy: overwrite_with_backup
|
|
893
|
+
variables: []
|
|
894
|
+
enabledByDefault: true
|
|
895
|
+
packs:
|
|
896
|
+
- core
|
|
897
|
+
|
|
898
|
+
- id: principle-attack-the-premise-skill
|
|
899
|
+
type: skill
|
|
900
|
+
sourceTemplate: .claude/skills/principle-attack-the-premise/SKILL.md
|
|
901
|
+
targetPath: .claude/skills/principle-attack-the-premise/SKILL.md
|
|
902
|
+
requires:
|
|
903
|
+
- core-skill-delivery
|
|
904
|
+
mergeStrategy: overwrite_with_backup
|
|
905
|
+
variables: []
|
|
906
|
+
enabledByDefault: true
|
|
907
|
+
packs:
|
|
908
|
+
- core
|
|
909
|
+
|
|
910
|
+
- id: principle-model-the-domain-skill
|
|
911
|
+
type: skill
|
|
912
|
+
sourceTemplate: .claude/skills/principle-model-the-domain/SKILL.md
|
|
913
|
+
targetPath: .claude/skills/principle-model-the-domain/SKILL.md
|
|
914
|
+
requires:
|
|
915
|
+
- core-skill-delivery
|
|
916
|
+
mergeStrategy: overwrite_with_backup
|
|
917
|
+
variables: []
|
|
918
|
+
enabledByDefault: true
|
|
919
|
+
packs:
|
|
920
|
+
- core
|
|
921
|
+
|
|
922
|
+
- id: principle-boundary-discipline-skill
|
|
923
|
+
type: skill
|
|
924
|
+
sourceTemplate: .claude/skills/principle-boundary-discipline/SKILL.md
|
|
925
|
+
targetPath: .claude/skills/principle-boundary-discipline/SKILL.md
|
|
926
|
+
requires:
|
|
927
|
+
- core-skill-delivery
|
|
928
|
+
mergeStrategy: overwrite_with_backup
|
|
929
|
+
variables: []
|
|
930
|
+
enabledByDefault: true
|
|
931
|
+
packs:
|
|
932
|
+
- core
|
|
933
|
+
|
|
934
|
+
- id: principle-migrate-callers-then-delete-legacy-apis-skill
|
|
935
|
+
type: skill
|
|
936
|
+
sourceTemplate: .claude/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md
|
|
937
|
+
targetPath: .claude/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md
|
|
938
|
+
requires:
|
|
939
|
+
- core-skill-delivery
|
|
940
|
+
mergeStrategy: overwrite_with_backup
|
|
941
|
+
variables: []
|
|
942
|
+
enabledByDefault: true
|
|
943
|
+
packs:
|
|
944
|
+
- core
|
|
945
|
+
|
|
946
|
+
- id: principle-prove-it-works-skill
|
|
947
|
+
type: skill
|
|
948
|
+
sourceTemplate: .claude/skills/principle-prove-it-works/SKILL.md
|
|
949
|
+
targetPath: .claude/skills/principle-prove-it-works/SKILL.md
|
|
950
|
+
requires:
|
|
951
|
+
- core-skill-delivery
|
|
952
|
+
mergeStrategy: overwrite_with_backup
|
|
953
|
+
variables: []
|
|
954
|
+
enabledByDefault: true
|
|
955
|
+
packs:
|
|
956
|
+
- core
|
|
957
|
+
|
|
958
|
+
- id: principle-fix-root-causes-skill
|
|
959
|
+
type: skill
|
|
960
|
+
sourceTemplate: .claude/skills/principle-fix-root-causes/SKILL.md
|
|
961
|
+
targetPath: .claude/skills/principle-fix-root-causes/SKILL.md
|
|
962
|
+
requires:
|
|
963
|
+
- core-skill-delivery
|
|
964
|
+
mergeStrategy: overwrite_with_backup
|
|
965
|
+
variables: []
|
|
966
|
+
enabledByDefault: true
|
|
967
|
+
packs:
|
|
968
|
+
- core
|
|
969
|
+
|
|
970
|
+
- id: principle-sequence-verifiable-units-skill
|
|
971
|
+
type: skill
|
|
972
|
+
sourceTemplate: .claude/skills/principle-sequence-verifiable-units/SKILL.md
|
|
973
|
+
targetPath: .claude/skills/principle-sequence-verifiable-units/SKILL.md
|
|
974
|
+
requires:
|
|
975
|
+
- core-skill-delivery
|
|
976
|
+
mergeStrategy: overwrite_with_backup
|
|
977
|
+
variables: []
|
|
978
|
+
enabledByDefault: true
|
|
979
|
+
packs:
|
|
980
|
+
- core
|
|
981
|
+
|
|
982
|
+
- id: principle-test-behavior-not-implementation-skill
|
|
983
|
+
type: skill
|
|
984
|
+
sourceTemplate: .claude/skills/principle-test-behavior-not-implementation/SKILL.md
|
|
985
|
+
targetPath: .claude/skills/principle-test-behavior-not-implementation/SKILL.md
|
|
986
|
+
requires:
|
|
987
|
+
- core-skill-delivery
|
|
988
|
+
mergeStrategy: overwrite_with_backup
|
|
989
|
+
variables: []
|
|
990
|
+
enabledByDefault: true
|
|
991
|
+
packs:
|
|
992
|
+
- core
|
|
993
|
+
|
|
994
|
+
- id: principle-guard-the-context-window-skill
|
|
995
|
+
type: skill
|
|
996
|
+
sourceTemplate: .claude/skills/principle-guard-the-context-window/SKILL.md
|
|
997
|
+
targetPath: .claude/skills/principle-guard-the-context-window/SKILL.md
|
|
998
|
+
requires:
|
|
999
|
+
- core-skill-delivery
|
|
1000
|
+
mergeStrategy: overwrite_with_backup
|
|
1001
|
+
variables: []
|
|
1002
|
+
enabledByDefault: true
|
|
1003
|
+
packs:
|
|
1004
|
+
- core
|
|
1005
|
+
|
|
1006
|
+
- id: principle-never-block-on-the-human-skill
|
|
1007
|
+
type: skill
|
|
1008
|
+
sourceTemplate: .claude/skills/principle-never-block-on-the-human/SKILL.md
|
|
1009
|
+
targetPath: .claude/skills/principle-never-block-on-the-human/SKILL.md
|
|
1010
|
+
requires:
|
|
1011
|
+
- core-skill-delivery
|
|
1012
|
+
mergeStrategy: overwrite_with_backup
|
|
1013
|
+
variables: []
|
|
1014
|
+
enabledByDefault: true
|
|
1015
|
+
packs:
|
|
1016
|
+
- core
|
|
1017
|
+
|
|
1018
|
+
- id: principle-encode-lessons-in-structure-skill
|
|
1019
|
+
type: skill
|
|
1020
|
+
sourceTemplate: .claude/skills/principle-encode-lessons-in-structure/SKILL.md
|
|
1021
|
+
targetPath: .claude/skills/principle-encode-lessons-in-structure/SKILL.md
|
|
1022
|
+
requires:
|
|
1023
|
+
- core-skill-delivery
|
|
1024
|
+
mergeStrategy: overwrite_with_backup
|
|
1025
|
+
variables: []
|
|
1026
|
+
enabledByDefault: true
|
|
1027
|
+
packs:
|
|
1028
|
+
- core
|
|
1029
|
+
|
|
848
1030
|
- id: agent-handoff-planner
|
|
849
1031
|
type: agent
|
|
850
1032
|
sourceTemplate: .claude/agents/handoff-planner.md
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
name: ukit-user-layer
|
|
3
|
+
level: user
|
|
4
|
+
autoDiscoverTemplates: false
|
|
5
|
+
description: >
|
|
6
|
+
UKit user-level manifest. Seeds ~/.ukit/ (resolved via os.homedir()) with personal
|
|
7
|
+
defaults: ownership README, minimal config, and playbook seeds. Every item is
|
|
8
|
+
mergeStrategy: skip — seed-once semantics; UKit never overwrites user files.
|
|
9
|
+
|
|
10
|
+
items:
|
|
11
|
+
- id: user-readme
|
|
12
|
+
type: config
|
|
13
|
+
sourceTemplate: user/README.md
|
|
14
|
+
targetPath: README.md
|
|
15
|
+
requires: []
|
|
16
|
+
mergeStrategy: skip
|
|
17
|
+
variables: []
|
|
18
|
+
enabledByDefault: true
|
|
19
|
+
packs:
|
|
20
|
+
- core
|
|
21
|
+
|
|
22
|
+
- id: user-config
|
|
23
|
+
type: config
|
|
24
|
+
sourceTemplate: user/storage/config.json
|
|
25
|
+
targetPath: storage/config.json
|
|
26
|
+
requires: []
|
|
27
|
+
mergeStrategy: skip
|
|
28
|
+
variables: []
|
|
29
|
+
enabledByDefault: true
|
|
30
|
+
packs:
|
|
31
|
+
- core
|
|
32
|
+
|
|
33
|
+
- id: user-playbook-bugfix
|
|
34
|
+
type: config
|
|
35
|
+
sourceTemplate: user/playbooks/bug-fix.md
|
|
36
|
+
targetPath: playbooks/bug-fix.md
|
|
37
|
+
requires: []
|
|
38
|
+
mergeStrategy: skip
|
|
39
|
+
variables: []
|
|
40
|
+
enabledByDefault: true
|
|
41
|
+
packs:
|
|
42
|
+
- core
|
|
43
|
+
|
|
44
|
+
- id: user-playbook-issue
|
|
45
|
+
type: config
|
|
46
|
+
sourceTemplate: user/playbooks/issue-implementation.md
|
|
47
|
+
targetPath: playbooks/issue-implementation.md
|
|
48
|
+
requires: []
|
|
49
|
+
mergeStrategy: skip
|
|
50
|
+
variables: []
|
|
51
|
+
enabledByDefault: true
|
|
52
|
+
packs:
|
|
53
|
+
- core
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ngockhoale/ukit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex and omp (Oh My Pi).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
"src/",
|
|
32
32
|
"scripts/",
|
|
33
33
|
"templates/",
|
|
34
|
+
"!templates/**/.smbdelete*",
|
|
35
|
+
"!templates/.smbdelete*",
|
|
34
36
|
"CHANGELOG.md",
|
|
35
37
|
"LICENSE",
|
|
36
38
|
"templates/CLAUDE.md",
|
package/src/cli/commands/diff.js
CHANGED
|
@@ -36,8 +36,10 @@ export async function runDiff({ packageRoot, projectRoot, packageVersion, argv =
|
|
|
36
36
|
console.log(`[UKit] Detected packs: ${result.stackContext.detectedPacks.join(', ')}`);
|
|
37
37
|
|
|
38
38
|
for (const row of result.rows) {
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
// User-layer rows live under ~/.ukit — outside projectRoot — so a relative
|
|
40
|
+
// path would be a noisy ../../… chain; print the absolute target instead.
|
|
41
|
+
const displayPath = row.layer === 'user' ? row.targetPath : path.relative(projectRoot, row.targetPath);
|
|
42
|
+
console.log(`- [${row.action}] ${row.id} -> ${displayPath}`);
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
console.log(
|
|
@@ -27,6 +27,9 @@ import { inspectUnattendedMode } from '../../core/unattendedDoctor.js';
|
|
|
27
27
|
import { inspectPermissions } from '../../core/permissionDoctor.js';
|
|
28
28
|
import { inspectHookChainHealth } from '../../core/hookChainDoctor.js';
|
|
29
29
|
import { findOpencodeArtifacts, opencodeSteerMessage } from '../../core/opencodeSteer.js';
|
|
30
|
+
import { buildUserPaths } from '../../core/userPaths.js';
|
|
31
|
+
import { loadPlaybooks } from '../../core/userPlaybooks.js';
|
|
32
|
+
import { userMemoryStats } from '../../core/memory/userMemory.js';
|
|
30
33
|
|
|
31
34
|
export const DOCTOR_HELP_FLAGS = new Set(['--help', '-h']);
|
|
32
35
|
const KNOWN_FLAGS = new Set([...DOCTOR_HELP_FLAGS, '--skills', '--gateway', '--docs', '--permissions', '--json']);
|
|
@@ -94,7 +97,7 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir =
|
|
|
94
97
|
const stack = await detectStack(projectRoot);
|
|
95
98
|
const providers = await detectProviders(projectRoot);
|
|
96
99
|
const installMeta = await readJsonIfExists(pathConfig.installMetaPath);
|
|
97
|
-
const runtimeConfigInspection = await inspectRuntimeConfig(projectRoot);
|
|
100
|
+
const runtimeConfigInspection = await inspectRuntimeConfig(projectRoot, { homeDir });
|
|
98
101
|
|
|
99
102
|
const trackedPaths = Array.isArray(installMeta?.files)
|
|
100
103
|
? installMeta.files
|
|
@@ -288,6 +291,24 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir =
|
|
|
288
291
|
}
|
|
289
292
|
console.log(`[UKit] ${ok(checks.allProvidersConfigured)} All providers configured`);
|
|
290
293
|
|
|
294
|
+
// FR-011 — user-layer health (~/.ukit). Advisory only: a missing or broken
|
|
295
|
+
// user layer never fails the project report.
|
|
296
|
+
const userPaths = buildUserPaths({ homeDir });
|
|
297
|
+
if (!(await pathExists(userPaths.userRoot))) {
|
|
298
|
+
console.log('[UKit] User layer: not seeded (run ukit install)');
|
|
299
|
+
} else {
|
|
300
|
+
const userConfigState = !runtimeConfigInspection.user.exists
|
|
301
|
+
? 'absent'
|
|
302
|
+
: runtimeConfigInspection.user.parseError
|
|
303
|
+
? 'invalid'
|
|
304
|
+
: 'ok';
|
|
305
|
+
const userPlaybooks = await loadPlaybooks(userPaths.playbooksDir);
|
|
306
|
+
const userStats = await userMemoryStats({ homeDir });
|
|
307
|
+
console.log(
|
|
308
|
+
`[UKit] User layer: present (${userPaths.userRoot}) | config: ${userConfigState} | playbooks: ${userPlaybooks.size} | memory records: ${userStats.total}`,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
291
312
|
console.log('[UKit] Project rules checks:');
|
|
292
313
|
for (const check of projectChecks) {
|
|
293
314
|
if (check.applicable === false) continue;
|
|
@@ -284,6 +284,16 @@ export async function runInstall({ packageRoot, projectRoot, packageVersion, arg
|
|
|
284
284
|
);
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
if (result.userLayer) {
|
|
288
|
+
if (result.userLayer.error) {
|
|
289
|
+
console.warn(`[UKit] Warning: user-layer seeding skipped — ${result.userLayer.error}`);
|
|
290
|
+
} else {
|
|
291
|
+
console.log(
|
|
292
|
+
`[UKit] User layer: seeded=${result.userLayer.seeded} kept=${result.userLayer.kept} (${result.userLayer.userRoot})`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
287
297
|
const providerNames = Object.keys(result.providerContext.providers);
|
|
288
298
|
const providerStatus = providerNames
|
|
289
299
|
.map((name) => `${name}=${result.providerContext.providers[name]?.supported ?? 'unknown'}`)
|
|
@@ -15,6 +15,15 @@ import {
|
|
|
15
15
|
stats as v2Stats,
|
|
16
16
|
updateRecord,
|
|
17
17
|
} from '../../core/memory/storeV2.js';
|
|
18
|
+
import {
|
|
19
|
+
addUserRecord,
|
|
20
|
+
getUserRecord,
|
|
21
|
+
loadUserRecords,
|
|
22
|
+
queryUserRecords,
|
|
23
|
+
saveUserRecords,
|
|
24
|
+
updateUserRecord,
|
|
25
|
+
userMemoryStats,
|
|
26
|
+
} from '../../core/memory/userMemory.js';
|
|
18
27
|
import { runMigration } from '../../core/memory/migrate.js';
|
|
19
28
|
import { getContextInjection, search } from '../../core/memory/retrieval.js';
|
|
20
29
|
import { inspectRuntimeConfig } from '../../core/runtimeConfig.js';
|
|
@@ -23,6 +32,7 @@ import { pathExists } from '../../core/fileOps.js';
|
|
|
23
32
|
import { detectProjectContext } from '../../context/detectProjectContext.js';
|
|
24
33
|
import { listLedgerFiles, LEDGER_DIR_REL } from '../../diagnostics/ledgerFiles.js';
|
|
25
34
|
import fs from 'node:fs/promises';
|
|
35
|
+
import os from 'node:os';
|
|
26
36
|
import path from 'node:path';
|
|
27
37
|
|
|
28
38
|
const HELP_FLAGS = new Set(['--help', '-h', 'help']);
|
|
@@ -180,6 +190,122 @@ async function runMemoryV2(projectRoot, args) {
|
|
|
180
190
|
throw new Error(`Unknown memory v2 op: ${op}. Expected list|show|promote|stale|migrate.`);
|
|
181
191
|
}
|
|
182
192
|
|
|
193
|
+
// ---- `ukit memory --user` — user-level store at ~/.ukit (FR-011) ----
|
|
194
|
+
// Same v2 record document, no v1→v2 migration (the user layer starts at v2).
|
|
195
|
+
// Ops: list|add|get|update|forget|stats; an optional `v2` token is accepted so
|
|
196
|
+
// `memory --user v2 add` and `memory --user add` are equivalent. Any other
|
|
197
|
+
// subcommand under --user is a misuse error — v1 lanes stay project-level.
|
|
198
|
+
|
|
199
|
+
const USER_MEMORY_OPS = new Set(['list', 'add', 'get', 'update', 'forget', 'stats']);
|
|
200
|
+
|
|
201
|
+
async function runUserMemory(homeDir, args) {
|
|
202
|
+
const rest = (args[0] ?? '').toLowerCase() === 'v2' ? args.slice(1) : args;
|
|
203
|
+
const op = (rest[0] ?? 'list').toLowerCase();
|
|
204
|
+
const opArgs = rest.slice(1);
|
|
205
|
+
|
|
206
|
+
if (!USER_MEMORY_OPS.has(op)) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`--user only supports v2 ops (list|add|get|update|forget|stats); got "${op}".`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (op === 'list') {
|
|
213
|
+
const typeFlag = extractFlag(opArgs, '--type');
|
|
214
|
+
const scopeFlag = extractFlag(typeFlag.rest, '--scope');
|
|
215
|
+
const statusFlag = extractFlag(scopeFlag.rest, '--status');
|
|
216
|
+
const records = await queryUserRecords({
|
|
217
|
+
type: typeFlag.value ?? undefined,
|
|
218
|
+
scope: scopeFlag.value ?? undefined,
|
|
219
|
+
status: statusFlag.value ?? undefined,
|
|
220
|
+
}, { homeDir });
|
|
221
|
+
if (records.length === 0) {
|
|
222
|
+
console.log('[UKit] No user memory records found.');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
for (const record of records) {
|
|
226
|
+
const scopeLabel = record.project_id ?? record.scope;
|
|
227
|
+
console.log(`${record.id} [${record.type}/${scopeLabel}/${record.status}] — ${record.text}`);
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (op === 'add') {
|
|
233
|
+
const typeFlag = extractFlag(opArgs, '--type');
|
|
234
|
+
const scopeFlag = extractFlag(typeFlag.rest, '--scope');
|
|
235
|
+
const provenanceFlag = extractFlag(scopeFlag.rest, '--provenance');
|
|
236
|
+
const text = provenanceFlag.rest.join(' ').trim();
|
|
237
|
+
if (!text) {
|
|
238
|
+
throw new Error('Missing record text. Usage: ukit memory --user add [--type <type>] [--scope <scope>] <text>');
|
|
239
|
+
}
|
|
240
|
+
const record = await addUserRecord({
|
|
241
|
+
type: typeFlag.value ?? 'derived_fact',
|
|
242
|
+
scope: scopeFlag.value ?? 'user',
|
|
243
|
+
text,
|
|
244
|
+
provenance: provenanceFlag.value ?? 'cli',
|
|
245
|
+
createdBy: 'cli',
|
|
246
|
+
}, { homeDir });
|
|
247
|
+
console.log(`[UKit] Added user record ${record.id}.`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (op === 'get') {
|
|
252
|
+
const id = opArgs.join(' ').trim();
|
|
253
|
+
if (!id) {
|
|
254
|
+
throw new Error('Missing record id. Usage: ukit memory --user get <id>');
|
|
255
|
+
}
|
|
256
|
+
const record = await getUserRecord(id, { homeDir });
|
|
257
|
+
if (!record) {
|
|
258
|
+
throw new Error(`User memory record not found: ${id}`);
|
|
259
|
+
}
|
|
260
|
+
console.log(JSON.stringify(record, null, 2));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (op === 'update') {
|
|
265
|
+
const id = opArgs[0];
|
|
266
|
+
if (!id) {
|
|
267
|
+
throw new Error('Missing record id. Usage: ukit memory --user update <id> [--status <s>] [--type <t>] [--scope <s>] [text]');
|
|
268
|
+
}
|
|
269
|
+
const statusFlag = extractFlag(opArgs.slice(1), '--status');
|
|
270
|
+
const typeFlag = extractFlag(statusFlag.rest, '--type');
|
|
271
|
+
const scopeFlag = extractFlag(typeFlag.rest, '--scope');
|
|
272
|
+
const text = scopeFlag.rest.join(' ').trim();
|
|
273
|
+
const patch = {};
|
|
274
|
+
if (statusFlag.value != null) patch.status = statusFlag.value;
|
|
275
|
+
if (typeFlag.value != null) patch.type = typeFlag.value;
|
|
276
|
+
if (scopeFlag.value != null) patch.scope = scopeFlag.value;
|
|
277
|
+
if (text) patch.text = text;
|
|
278
|
+
if (Object.keys(patch).length === 0) {
|
|
279
|
+
throw new Error('Nothing to update. Pass --status/--type/--scope or new text.');
|
|
280
|
+
}
|
|
281
|
+
const updated = await updateUserRecord(id, patch, { homeDir });
|
|
282
|
+
if (!updated) {
|
|
283
|
+
throw new Error(`User memory record not found: ${id}`);
|
|
284
|
+
}
|
|
285
|
+
console.log(`[UKit] Updated user record ${updated.id}.`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (op === 'forget') {
|
|
290
|
+
const id = opArgs.join(' ').trim();
|
|
291
|
+
if (!id) {
|
|
292
|
+
throw new Error('Missing record id. Usage: ukit memory --user forget <id>');
|
|
293
|
+
}
|
|
294
|
+
const records = await loadUserRecords({ homeDir });
|
|
295
|
+
const remaining = records.filter((r) => r.id !== id);
|
|
296
|
+
if (remaining.length === records.length) {
|
|
297
|
+
throw new Error(`User memory record not found: ${id}`);
|
|
298
|
+
}
|
|
299
|
+
await saveUserRecords(remaining, { homeDir });
|
|
300
|
+
console.log(`[UKit] Forgot user record ${id}.`);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// op === 'stats'
|
|
305
|
+
const stats = await userMemoryStats({ homeDir });
|
|
306
|
+
console.log(`[UKit] User memory: total=${stats.total} byType=${JSON.stringify(stats.byType)} byStatus=${JSON.stringify(stats.byStatus)} invalidSkipped=${stats.invalidSkipped}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
183
309
|
// ---- `ukit memory promote` — MEMORY.md `## ukit-learned` lane (SPEC §7a) ----
|
|
184
310
|
|
|
185
311
|
const LEARNED_START = '<!-- ukit-learned:start -->';
|
|
@@ -366,13 +492,19 @@ async function runMemoryEpisode(projectRoot, args) {
|
|
|
366
492
|
console.log(`[UKit] episode: recorded ${record.id} for session ${sessionId}`);
|
|
367
493
|
}
|
|
368
494
|
|
|
369
|
-
export async function runMemory({ projectRoot, argv = [] }) {
|
|
495
|
+
export async function runMemory({ projectRoot, homeDir = os.homedir(), argv = [] }) {
|
|
496
|
+
const userFlagIndex = argv.indexOf('--user');
|
|
497
|
+
const isUser = userFlagIndex >= 0;
|
|
370
498
|
const runtimePaths = buildRuntimePaths(projectRoot);
|
|
371
|
-
|
|
499
|
+
// --user operates on ~/.ukit and does not require the project runtime.
|
|
500
|
+
if (!isUser && !(await pathExists(runtimePaths.runtimeRoot))) {
|
|
372
501
|
throw new Error('Shared UKit runtime not found. Run `ukit install` first.');
|
|
373
502
|
}
|
|
374
503
|
|
|
375
|
-
const
|
|
504
|
+
const argvRest = isUser
|
|
505
|
+
? [...argv.slice(0, userFlagIndex), ...argv.slice(userFlagIndex + 1)]
|
|
506
|
+
: argv;
|
|
507
|
+
const [subcommandRaw, ...rest] = argvRest;
|
|
376
508
|
const subcommand = (subcommandRaw ?? 'list').toLowerCase();
|
|
377
509
|
|
|
378
510
|
if (HELP_FLAGS.has(subcommand)) {
|
|
@@ -380,6 +512,11 @@ export async function runMemory({ projectRoot, argv = [] }) {
|
|
|
380
512
|
return;
|
|
381
513
|
}
|
|
382
514
|
|
|
515
|
+
if (isUser) {
|
|
516
|
+
await runUserMemory(homeDir, argvRest);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
|
|
383
520
|
if (subcommand === 'v2') {
|
|
384
521
|
await runMemoryV2(projectRoot, rest);
|
|
385
522
|
return;
|
|
@@ -615,4 +752,6 @@ export function printMemoryHelp() {
|
|
|
615
752
|
console.log(' promote [--dry-run] Render approved rules/procedures into the ## ukit-learned block in docs/MEMORY.md');
|
|
616
753
|
console.log(' episode [--dry-run] [--session <id>] Write a session episode record from the exec-ledger');
|
|
617
754
|
console.log(' hygiene [--project <id>] Run decision-conflict resolution + session archiving now');
|
|
755
|
+
console.log(' v2 <list|show|promote|stale|migrate> Operate on the project v2 record store');
|
|
756
|
+
console.log(' --user [v2] <list|add|get|update|forget|stats> Operate on the user-level store at ~/.ukit/storage/memory/v2/records.json');
|
|
618
757
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { listPlaybooks, resolvePlaybook } from '../../core/userPlaybooks.js';
|
|
2
|
+
|
|
3
|
+
const HELP_FLAGS = new Set(['--help', '-h', 'help']);
|
|
4
|
+
|
|
5
|
+
export function printPlaybookHelp() {
|
|
6
|
+
console.log('UKit Playbook Commands');
|
|
7
|
+
console.log('Usage: ukit playbook <list|show> [args]');
|
|
8
|
+
console.log('');
|
|
9
|
+
console.log('Subcommands:');
|
|
10
|
+
console.log(' list List resolved playbooks (id | source | lanes)');
|
|
11
|
+
console.log(' show <id> Print the resolved playbook body and its source level');
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log('Resolution precedence: project (.ukit/playbooks) > user (~/.ukit/playbooks) > builtin.');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function runPlaybook({ projectRoot, homeDir, argv = [], builtins } = {}) {
|
|
17
|
+
const [subcommandRaw, ...rest] = argv;
|
|
18
|
+
const subcommand = (subcommandRaw ?? 'list').toLowerCase();
|
|
19
|
+
|
|
20
|
+
if (HELP_FLAGS.has(subcommand)) {
|
|
21
|
+
printPlaybookHelp();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (subcommand === 'list') {
|
|
26
|
+
const rows = await listPlaybooks({ projectRoot, homeDir, builtins });
|
|
27
|
+
if (rows.length === 0) {
|
|
28
|
+
console.log('No playbooks found (built-ins: bug-fix, issue-implementation)');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log('id | source | lanes');
|
|
32
|
+
for (const row of rows) {
|
|
33
|
+
console.log(`${row.id} | ${row.source} | ${row.lanes.join(', ') || '-'}`);
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (subcommand === 'show') {
|
|
39
|
+
const id = rest[0];
|
|
40
|
+
if (!id) {
|
|
41
|
+
throw new Error('Missing playbook id. Usage: ukit playbook show <id>');
|
|
42
|
+
}
|
|
43
|
+
const playbook = await resolvePlaybook(id, { projectRoot, homeDir, builtins });
|
|
44
|
+
if (playbook === null) {
|
|
45
|
+
throw new Error(`Unknown playbook: ${id}`);
|
|
46
|
+
}
|
|
47
|
+
console.log(`source: ${playbook.source}`);
|
|
48
|
+
console.log(playbook.body);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw new Error(`Unknown playbook subcommand: ${subcommand}. Run ukit playbook --help for usage.`);
|
|
53
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { runUpdate } from './commands/update.js';
|
|
|
9
9
|
import { runCode } from './commands/code.js';
|
|
10
10
|
import { runMetrics } from './commands/metrics.js';
|
|
11
11
|
import { runFeedback } from './commands/feedback.js';
|
|
12
|
+
import { runPlaybook } from './commands/playbook.js';
|
|
12
13
|
|
|
13
14
|
const GLOBAL_FLAGS = new Set(['--help', '-h', '--version', '-v']);
|
|
14
15
|
|
|
@@ -67,6 +68,11 @@ export async function runCli({ argv, packageRoot, projectRoot, packageVersion })
|
|
|
67
68
|
return;
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
if (command === 'playbook') {
|
|
72
|
+
await runPlaybook({ projectRoot, argv: commandArgv });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
70
76
|
if (command === 'metrics') {
|
|
71
77
|
await runMetrics({ projectRoot, argv: commandArgv });
|
|
72
78
|
return;
|
|
@@ -110,6 +116,7 @@ export async function runCli({ argv, packageRoot, projectRoot, packageVersion })
|
|
|
110
116
|
console.log(' code Context compiler surface (peek/search/context/impact)');
|
|
111
117
|
console.log(' status Show UKit runtime status');
|
|
112
118
|
console.log(' memory Inspect shared UKit memory');
|
|
119
|
+
console.log(' playbook List/show playbooks (project > user > builtin)');
|
|
113
120
|
console.log(' metrics Telemetry roll-up (route outcomes, failure patterns, memory)');
|
|
114
121
|
console.log(' feedback Record or list wrong-route feedback labels');
|
|
115
122
|
console.log(' update Upgrade the global UKit CLI to the latest version');
|