haki-skills 0.2.1 → 0.2.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/.agent/bin/haki-tools.cjs +130 -130
- package/.agent/bin/lib/config.cjs +170 -170
- package/.agent/bin/lib/core.cjs +203 -203
- package/.agent/bin/lib/roadmap.cjs +266 -266
- package/.agent/bin/lib/state.cjs +187 -187
- package/.agent/references/questioning.md +95 -95
- package/.agent/references/ui-brand.md +50 -50
- package/.agent/skills/api-testing/SKILL.md +422 -422
- package/.agent/skills/api-testing/templates/api-client.ts +84 -84
- package/.agent/skills/api-testing/templates/example.api.test.ts +106 -106
- package/.agent/skills/api-testing/templates/seed-data.json +20 -20
- package/.agent/skills/api-testing/templates/vitest.api.config.ts +39 -39
- package/.agent/skills/context7-research/SKILL.md +141 -141
- package/.agent/skills/playwright-automation/SKILL.md +382 -382
- package/.agent/skills/playwright-automation/templates/base.fixture.ts +33 -33
- package/.agent/skills/playwright-automation/templates/example.page.ts +45 -45
- package/.agent/skills/playwright-automation/templates/example.spec.ts +43 -43
- package/.agent/skills/playwright-automation/templates/playwright.config.ts +71 -71
- package/.agent/skills/ui-ux-pro-max/SKILL.md +45 -45
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -26
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +161 -161
- package/.agent/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -1924
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +35 -35
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -68
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -162
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -114
- package/.agent/skills/user-docs-generator/SKILL.md +123 -123
- package/.agent/templates/agents.md +46 -46
- package/.agent/templates/claude.md +13 -13
- package/.agent/templates/config.json +15 -15
- package/.agent/templates/cursor-haki.mdc +17 -17
- package/.agent/templates/project.md +27 -27
- package/.agent/templates/roadmap.md +22 -22
- package/.agent/templates/task.md +94 -94
- package/.agent/templates/user-docs-index.md +9 -9
- package/.agent/templates/user-docs-module.md +59 -59
- package/.agent/workflows/haki-api-test.md +175 -175
- package/.agent/workflows/haki-discuss.md +61 -61
- package/.agent/workflows/haki-docs.md +80 -80
- package/.agent/workflows/haki-e2e-gen.md +82 -82
- package/.agent/workflows/haki-e2e.md +181 -181
- package/.agent/workflows/haki-exec.md +77 -77
- package/.agent/workflows/haki-init.md +36 -36
- package/.agent/workflows/haki-map-codebase.md +30 -30
- package/.agent/workflows/haki-new-milestone.md +73 -73
- package/.agent/workflows/haki-new-project.md +69 -69
- package/.agent/workflows/haki-next.md +63 -63
- package/.agent/workflows/haki-plan.md +58 -58
- package/.agent/workflows/haki-research.md +93 -93
- package/README.md +169 -169
- package/bin/install.js +304 -239
- package/package.json +43 -43
|
@@ -1,130 +1,130 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Haki Tools — CLI utility for haki workflow operations
|
|
5
|
-
*
|
|
6
|
-
* Lean CLI inspired by gsd-tools.cjs but focused on 4 core concerns:
|
|
7
|
-
* - config: .haki/config.json CRUD
|
|
8
|
-
* - roadmap: ROADMAP.md parsing and status updates
|
|
9
|
-
* - state: Project state detection for /haki:next routing
|
|
10
|
-
* - core: Shared path resolution and utilities
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* node haki-tools.cjs <command> [args]
|
|
14
|
-
*
|
|
15
|
-
* Config Operations:
|
|
16
|
-
* config init [choices-json] Initialize config.json
|
|
17
|
-
* config get <key.path> Get config value
|
|
18
|
-
* config set <key.path> <value> Set config value
|
|
19
|
-
*
|
|
20
|
-
* Roadmap Operations:
|
|
21
|
-
* roadmap analyze Full roadmap parse with stats
|
|
22
|
-
* roadmap next-task Find next actionable task
|
|
23
|
-
* roadmap update-status <id> <status> Update task status
|
|
24
|
-
*
|
|
25
|
-
* State Operations:
|
|
26
|
-
* state detect Full state detection (JSON)
|
|
27
|
-
* state json Compact state for /haki:next
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
const fs = require("fs");
|
|
31
|
-
const path = require("path");
|
|
32
|
-
const { findProjectRoot, error, output } = require("./lib/core.cjs");
|
|
33
|
-
const {
|
|
34
|
-
cmdConfigInit,
|
|
35
|
-
cmdConfigGet,
|
|
36
|
-
cmdConfigSet,
|
|
37
|
-
} = require("./lib/config.cjs");
|
|
38
|
-
const {
|
|
39
|
-
cmdRoadmapAnalyze,
|
|
40
|
-
cmdRoadmapNextTask,
|
|
41
|
-
cmdRoadmapUpdateStatus,
|
|
42
|
-
} = require("./lib/roadmap.cjs");
|
|
43
|
-
const { cmdStateDetect, cmdStateJson } = require("./lib/state.cjs");
|
|
44
|
-
|
|
45
|
-
function main() {
|
|
46
|
-
const args = process.argv.slice(2);
|
|
47
|
-
const raw = args.includes("--raw");
|
|
48
|
-
const filteredArgs = args.filter((a) => a !== "--raw");
|
|
49
|
-
|
|
50
|
-
if (filteredArgs.length === 0) {
|
|
51
|
-
error(
|
|
52
|
-
"Usage: haki-tools <command> [args]\n\nCommands:\n config init|get|set\n roadmap analyze|next-task|update-status\n state detect|json",
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const cwd = findProjectRoot(process.cwd());
|
|
57
|
-
const command = filteredArgs[0];
|
|
58
|
-
const subCommand = filteredArgs[1];
|
|
59
|
-
const restArgs = filteredArgs.slice(2);
|
|
60
|
-
|
|
61
|
-
switch (command) {
|
|
62
|
-
// ─── Config ────────────────────────────────────────────────────────
|
|
63
|
-
case "config":
|
|
64
|
-
case "config-init":
|
|
65
|
-
case "config-get":
|
|
66
|
-
case "config-set": {
|
|
67
|
-
const sub = command === "config" ? subCommand : command.split("-")[1];
|
|
68
|
-
|
|
69
|
-
switch (sub) {
|
|
70
|
-
case "init":
|
|
71
|
-
cmdConfigInit(cwd, restArgs[0] || filteredArgs[2], raw);
|
|
72
|
-
break;
|
|
73
|
-
case "get":
|
|
74
|
-
cmdConfigGet(cwd, restArgs[0] || filteredArgs[2], raw);
|
|
75
|
-
break;
|
|
76
|
-
case "set":
|
|
77
|
-
cmdConfigSet(
|
|
78
|
-
cwd,
|
|
79
|
-
restArgs[0] || filteredArgs[2],
|
|
80
|
-
restArgs[1] || filteredArgs[3],
|
|
81
|
-
raw,
|
|
82
|
-
);
|
|
83
|
-
break;
|
|
84
|
-
default:
|
|
85
|
-
error("Usage: config <init|get|set> [args]");
|
|
86
|
-
}
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ─── Roadmap ──────────────────────────────────────────────────────
|
|
91
|
-
case "roadmap": {
|
|
92
|
-
switch (subCommand) {
|
|
93
|
-
case "analyze":
|
|
94
|
-
cmdRoadmapAnalyze(cwd, raw);
|
|
95
|
-
break;
|
|
96
|
-
case "next-task":
|
|
97
|
-
cmdRoadmapNextTask(cwd, raw);
|
|
98
|
-
break;
|
|
99
|
-
case "update-status":
|
|
100
|
-
cmdRoadmapUpdateStatus(cwd, restArgs[0], restArgs[1], raw);
|
|
101
|
-
break;
|
|
102
|
-
default:
|
|
103
|
-
error("Usage: roadmap <analyze|next-task|update-status> [args]");
|
|
104
|
-
}
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ─── State ────────────────────────────────────────────────────────
|
|
109
|
-
case "state": {
|
|
110
|
-
switch (subCommand) {
|
|
111
|
-
case "detect":
|
|
112
|
-
cmdStateDetect(cwd, raw);
|
|
113
|
-
break;
|
|
114
|
-
case "json":
|
|
115
|
-
cmdStateJson(cwd, raw);
|
|
116
|
-
break;
|
|
117
|
-
default:
|
|
118
|
-
error("Usage: state <detect|json>");
|
|
119
|
-
}
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
default:
|
|
124
|
-
error(
|
|
125
|
-
`Unknown command: ${command}\n\nValid commands: config, roadmap, state`,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Haki Tools — CLI utility for haki workflow operations
|
|
5
|
+
*
|
|
6
|
+
* Lean CLI inspired by gsd-tools.cjs but focused on 4 core concerns:
|
|
7
|
+
* - config: .haki/config.json CRUD
|
|
8
|
+
* - roadmap: ROADMAP.md parsing and status updates
|
|
9
|
+
* - state: Project state detection for /haki:next routing
|
|
10
|
+
* - core: Shared path resolution and utilities
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node haki-tools.cjs <command> [args]
|
|
14
|
+
*
|
|
15
|
+
* Config Operations:
|
|
16
|
+
* config init [choices-json] Initialize config.json
|
|
17
|
+
* config get <key.path> Get config value
|
|
18
|
+
* config set <key.path> <value> Set config value
|
|
19
|
+
*
|
|
20
|
+
* Roadmap Operations:
|
|
21
|
+
* roadmap analyze Full roadmap parse with stats
|
|
22
|
+
* roadmap next-task Find next actionable task
|
|
23
|
+
* roadmap update-status <id> <status> Update task status
|
|
24
|
+
*
|
|
25
|
+
* State Operations:
|
|
26
|
+
* state detect Full state detection (JSON)
|
|
27
|
+
* state json Compact state for /haki:next
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const fs = require("fs");
|
|
31
|
+
const path = require("path");
|
|
32
|
+
const { findProjectRoot, error, output } = require("./lib/core.cjs");
|
|
33
|
+
const {
|
|
34
|
+
cmdConfigInit,
|
|
35
|
+
cmdConfigGet,
|
|
36
|
+
cmdConfigSet,
|
|
37
|
+
} = require("./lib/config.cjs");
|
|
38
|
+
const {
|
|
39
|
+
cmdRoadmapAnalyze,
|
|
40
|
+
cmdRoadmapNextTask,
|
|
41
|
+
cmdRoadmapUpdateStatus,
|
|
42
|
+
} = require("./lib/roadmap.cjs");
|
|
43
|
+
const { cmdStateDetect, cmdStateJson } = require("./lib/state.cjs");
|
|
44
|
+
|
|
45
|
+
function main() {
|
|
46
|
+
const args = process.argv.slice(2);
|
|
47
|
+
const raw = args.includes("--raw");
|
|
48
|
+
const filteredArgs = args.filter((a) => a !== "--raw");
|
|
49
|
+
|
|
50
|
+
if (filteredArgs.length === 0) {
|
|
51
|
+
error(
|
|
52
|
+
"Usage: haki-tools <command> [args]\n\nCommands:\n config init|get|set\n roadmap analyze|next-task|update-status\n state detect|json",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const cwd = findProjectRoot(process.cwd());
|
|
57
|
+
const command = filteredArgs[0];
|
|
58
|
+
const subCommand = filteredArgs[1];
|
|
59
|
+
const restArgs = filteredArgs.slice(2);
|
|
60
|
+
|
|
61
|
+
switch (command) {
|
|
62
|
+
// ─── Config ────────────────────────────────────────────────────────
|
|
63
|
+
case "config":
|
|
64
|
+
case "config-init":
|
|
65
|
+
case "config-get":
|
|
66
|
+
case "config-set": {
|
|
67
|
+
const sub = command === "config" ? subCommand : command.split("-")[1];
|
|
68
|
+
|
|
69
|
+
switch (sub) {
|
|
70
|
+
case "init":
|
|
71
|
+
cmdConfigInit(cwd, restArgs[0] || filteredArgs[2], raw);
|
|
72
|
+
break;
|
|
73
|
+
case "get":
|
|
74
|
+
cmdConfigGet(cwd, restArgs[0] || filteredArgs[2], raw);
|
|
75
|
+
break;
|
|
76
|
+
case "set":
|
|
77
|
+
cmdConfigSet(
|
|
78
|
+
cwd,
|
|
79
|
+
restArgs[0] || filteredArgs[2],
|
|
80
|
+
restArgs[1] || filteredArgs[3],
|
|
81
|
+
raw,
|
|
82
|
+
);
|
|
83
|
+
break;
|
|
84
|
+
default:
|
|
85
|
+
error("Usage: config <init|get|set> [args]");
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Roadmap ──────────────────────────────────────────────────────
|
|
91
|
+
case "roadmap": {
|
|
92
|
+
switch (subCommand) {
|
|
93
|
+
case "analyze":
|
|
94
|
+
cmdRoadmapAnalyze(cwd, raw);
|
|
95
|
+
break;
|
|
96
|
+
case "next-task":
|
|
97
|
+
cmdRoadmapNextTask(cwd, raw);
|
|
98
|
+
break;
|
|
99
|
+
case "update-status":
|
|
100
|
+
cmdRoadmapUpdateStatus(cwd, restArgs[0], restArgs[1], raw);
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
error("Usage: roadmap <analyze|next-task|update-status> [args]");
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── State ────────────────────────────────────────────────────────
|
|
109
|
+
case "state": {
|
|
110
|
+
switch (subCommand) {
|
|
111
|
+
case "detect":
|
|
112
|
+
cmdStateDetect(cwd, raw);
|
|
113
|
+
break;
|
|
114
|
+
case "json":
|
|
115
|
+
cmdStateJson(cwd, raw);
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
error("Usage: state <detect|json>");
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
default:
|
|
124
|
+
error(
|
|
125
|
+
`Unknown command: ${command}\n\nValid commands: config, roadmap, state`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
main();
|
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Config — .haki/config.json CRUD operations
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const fs = require("fs");
|
|
6
|
-
const path = require("path");
|
|
7
|
-
const { output, error, hakiPaths, ensureDir } = require("./core.cjs");
|
|
8
|
-
|
|
9
|
-
const DEFAULT_CONFIG = {
|
|
10
|
-
project: {
|
|
11
|
-
name: "",
|
|
12
|
-
created: "",
|
|
13
|
-
},
|
|
14
|
-
ui_design_skill: "ui-ux-pro-max",
|
|
15
|
-
ui_design_variant: null,
|
|
16
|
-
workflow: {
|
|
17
|
-
auto_research: true,
|
|
18
|
-
auto_commit: true,
|
|
19
|
-
tdd_first: true,
|
|
20
|
-
plan_review_loops: 3,
|
|
21
|
-
parallelization: true,
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Build a config for a new project, merging defaults with user choices.
|
|
27
|
-
*/
|
|
28
|
-
function buildNewProjectConfig(userChoices = {}) {
|
|
29
|
-
return {
|
|
30
|
-
...DEFAULT_CONFIG,
|
|
31
|
-
...userChoices,
|
|
32
|
-
project: {
|
|
33
|
-
...DEFAULT_CONFIG.project,
|
|
34
|
-
...(userChoices.project || {}),
|
|
35
|
-
created: userChoices.project?.created || new Date().toISOString(),
|
|
36
|
-
},
|
|
37
|
-
workflow: {
|
|
38
|
-
...DEFAULT_CONFIG.workflow,
|
|
39
|
-
...(userChoices.workflow || {}),
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Initialize config.json for a new project.
|
|
46
|
-
* Idempotent: returns existing if already present.
|
|
47
|
-
*/
|
|
48
|
-
function cmdConfigInit(cwd, choicesJson, raw) {
|
|
49
|
-
const paths = hakiPaths(cwd);
|
|
50
|
-
const configPath = paths.config;
|
|
51
|
-
|
|
52
|
-
if (fs.existsSync(configPath)) {
|
|
53
|
-
output({ created: false, reason: "already_exists" }, raw, "exists");
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let userChoices = {};
|
|
58
|
-
if (choicesJson && choicesJson.trim() !== "") {
|
|
59
|
-
try {
|
|
60
|
-
userChoices = JSON.parse(choicesJson);
|
|
61
|
-
} catch (err) {
|
|
62
|
-
error("Invalid JSON for config init: " + err.message);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
ensureDir(paths.haki);
|
|
67
|
-
const config = buildNewProjectConfig(userChoices);
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
71
|
-
output({ created: true, path: ".haki/config.json" }, raw, "created");
|
|
72
|
-
} catch (err) {
|
|
73
|
-
error("Failed to write config.json: " + err.message);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get a config value using dot-notation path.
|
|
79
|
-
*/
|
|
80
|
-
function cmdConfigGet(cwd, keyPath, raw) {
|
|
81
|
-
const configPath = hakiPaths(cwd).config;
|
|
82
|
-
|
|
83
|
-
if (!keyPath) error("Usage: config get <key.path>");
|
|
84
|
-
|
|
85
|
-
let config = {};
|
|
86
|
-
try {
|
|
87
|
-
if (fs.existsSync(configPath)) {
|
|
88
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
89
|
-
} else {
|
|
90
|
-
error("No config.json found");
|
|
91
|
-
}
|
|
92
|
-
} catch (err) {
|
|
93
|
-
if (err.message.startsWith("No config")) throw err;
|
|
94
|
-
error("Failed to read config.json: " + err.message);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const keys = keyPath.split(".");
|
|
98
|
-
let current = config;
|
|
99
|
-
for (const key of keys) {
|
|
100
|
-
if (
|
|
101
|
-
current === undefined ||
|
|
102
|
-
current === null ||
|
|
103
|
-
typeof current !== "object"
|
|
104
|
-
) {
|
|
105
|
-
error("Key not found: " + keyPath);
|
|
106
|
-
}
|
|
107
|
-
current = current[key];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (current === undefined) error("Key not found: " + keyPath);
|
|
111
|
-
output(current, raw, String(current));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Set a config value using dot-notation path.
|
|
116
|
-
*/
|
|
117
|
-
function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
118
|
-
if (!keyPath) error("Usage: config set <key.path> <value>");
|
|
119
|
-
|
|
120
|
-
const configPath = hakiPaths(cwd).config;
|
|
121
|
-
|
|
122
|
-
let config = {};
|
|
123
|
-
try {
|
|
124
|
-
if (fs.existsSync(configPath)) {
|
|
125
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
126
|
-
}
|
|
127
|
-
} catch (err) {
|
|
128
|
-
error("Failed to read config.json: " + err.message);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Parse value
|
|
132
|
-
let parsedValue = value;
|
|
133
|
-
if (value === "true") parsedValue = true;
|
|
134
|
-
else if (value === "false") parsedValue = false;
|
|
135
|
-
else if (value === "null") parsedValue = null;
|
|
136
|
-
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
137
|
-
|
|
138
|
-
// Set nested value
|
|
139
|
-
const keys = keyPath.split(".");
|
|
140
|
-
let current = config;
|
|
141
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
142
|
-
if (
|
|
143
|
-
current[keys[i]] === undefined ||
|
|
144
|
-
typeof current[keys[i]] !== "object"
|
|
145
|
-
) {
|
|
146
|
-
current[keys[i]] = {};
|
|
147
|
-
}
|
|
148
|
-
current = current[keys[i]];
|
|
149
|
-
}
|
|
150
|
-
current[keys[keys.length - 1]] = parsedValue;
|
|
151
|
-
|
|
152
|
-
try {
|
|
153
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
154
|
-
output(
|
|
155
|
-
{ updated: true, key: keyPath, value: parsedValue },
|
|
156
|
-
raw,
|
|
157
|
-
`${keyPath}=${parsedValue}`,
|
|
158
|
-
);
|
|
159
|
-
} catch (err) {
|
|
160
|
-
error("Failed to write config.json: " + err.message);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
module.exports = {
|
|
165
|
-
DEFAULT_CONFIG,
|
|
166
|
-
buildNewProjectConfig,
|
|
167
|
-
cmdConfigInit,
|
|
168
|
-
cmdConfigGet,
|
|
169
|
-
cmdConfigSet,
|
|
170
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Config — .haki/config.json CRUD operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const { output, error, hakiPaths, ensureDir } = require("./core.cjs");
|
|
8
|
+
|
|
9
|
+
const DEFAULT_CONFIG = {
|
|
10
|
+
project: {
|
|
11
|
+
name: "",
|
|
12
|
+
created: "",
|
|
13
|
+
},
|
|
14
|
+
ui_design_skill: "ui-ux-pro-max",
|
|
15
|
+
ui_design_variant: null,
|
|
16
|
+
workflow: {
|
|
17
|
+
auto_research: true,
|
|
18
|
+
auto_commit: true,
|
|
19
|
+
tdd_first: true,
|
|
20
|
+
plan_review_loops: 3,
|
|
21
|
+
parallelization: true,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build a config for a new project, merging defaults with user choices.
|
|
27
|
+
*/
|
|
28
|
+
function buildNewProjectConfig(userChoices = {}) {
|
|
29
|
+
return {
|
|
30
|
+
...DEFAULT_CONFIG,
|
|
31
|
+
...userChoices,
|
|
32
|
+
project: {
|
|
33
|
+
...DEFAULT_CONFIG.project,
|
|
34
|
+
...(userChoices.project || {}),
|
|
35
|
+
created: userChoices.project?.created || new Date().toISOString(),
|
|
36
|
+
},
|
|
37
|
+
workflow: {
|
|
38
|
+
...DEFAULT_CONFIG.workflow,
|
|
39
|
+
...(userChoices.workflow || {}),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Initialize config.json for a new project.
|
|
46
|
+
* Idempotent: returns existing if already present.
|
|
47
|
+
*/
|
|
48
|
+
function cmdConfigInit(cwd, choicesJson, raw) {
|
|
49
|
+
const paths = hakiPaths(cwd);
|
|
50
|
+
const configPath = paths.config;
|
|
51
|
+
|
|
52
|
+
if (fs.existsSync(configPath)) {
|
|
53
|
+
output({ created: false, reason: "already_exists" }, raw, "exists");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let userChoices = {};
|
|
58
|
+
if (choicesJson && choicesJson.trim() !== "") {
|
|
59
|
+
try {
|
|
60
|
+
userChoices = JSON.parse(choicesJson);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
error("Invalid JSON for config init: " + err.message);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
ensureDir(paths.haki);
|
|
67
|
+
const config = buildNewProjectConfig(userChoices);
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
71
|
+
output({ created: true, path: ".haki/config.json" }, raw, "created");
|
|
72
|
+
} catch (err) {
|
|
73
|
+
error("Failed to write config.json: " + err.message);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get a config value using dot-notation path.
|
|
79
|
+
*/
|
|
80
|
+
function cmdConfigGet(cwd, keyPath, raw) {
|
|
81
|
+
const configPath = hakiPaths(cwd).config;
|
|
82
|
+
|
|
83
|
+
if (!keyPath) error("Usage: config get <key.path>");
|
|
84
|
+
|
|
85
|
+
let config = {};
|
|
86
|
+
try {
|
|
87
|
+
if (fs.existsSync(configPath)) {
|
|
88
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
89
|
+
} else {
|
|
90
|
+
error("No config.json found");
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (err.message.startsWith("No config")) throw err;
|
|
94
|
+
error("Failed to read config.json: " + err.message);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const keys = keyPath.split(".");
|
|
98
|
+
let current = config;
|
|
99
|
+
for (const key of keys) {
|
|
100
|
+
if (
|
|
101
|
+
current === undefined ||
|
|
102
|
+
current === null ||
|
|
103
|
+
typeof current !== "object"
|
|
104
|
+
) {
|
|
105
|
+
error("Key not found: " + keyPath);
|
|
106
|
+
}
|
|
107
|
+
current = current[key];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (current === undefined) error("Key not found: " + keyPath);
|
|
111
|
+
output(current, raw, String(current));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Set a config value using dot-notation path.
|
|
116
|
+
*/
|
|
117
|
+
function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
118
|
+
if (!keyPath) error("Usage: config set <key.path> <value>");
|
|
119
|
+
|
|
120
|
+
const configPath = hakiPaths(cwd).config;
|
|
121
|
+
|
|
122
|
+
let config = {};
|
|
123
|
+
try {
|
|
124
|
+
if (fs.existsSync(configPath)) {
|
|
125
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
error("Failed to read config.json: " + err.message);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Parse value
|
|
132
|
+
let parsedValue = value;
|
|
133
|
+
if (value === "true") parsedValue = true;
|
|
134
|
+
else if (value === "false") parsedValue = false;
|
|
135
|
+
else if (value === "null") parsedValue = null;
|
|
136
|
+
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
137
|
+
|
|
138
|
+
// Set nested value
|
|
139
|
+
const keys = keyPath.split(".");
|
|
140
|
+
let current = config;
|
|
141
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
142
|
+
if (
|
|
143
|
+
current[keys[i]] === undefined ||
|
|
144
|
+
typeof current[keys[i]] !== "object"
|
|
145
|
+
) {
|
|
146
|
+
current[keys[i]] = {};
|
|
147
|
+
}
|
|
148
|
+
current = current[keys[i]];
|
|
149
|
+
}
|
|
150
|
+
current[keys[keys.length - 1]] = parsedValue;
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
154
|
+
output(
|
|
155
|
+
{ updated: true, key: keyPath, value: parsedValue },
|
|
156
|
+
raw,
|
|
157
|
+
`${keyPath}=${parsedValue}`,
|
|
158
|
+
);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
error("Failed to write config.json: " + err.message);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
DEFAULT_CONFIG,
|
|
166
|
+
buildNewProjectConfig,
|
|
167
|
+
cmdConfigInit,
|
|
168
|
+
cmdConfigGet,
|
|
169
|
+
cmdConfigSet,
|
|
170
|
+
};
|