macca-method 2.1.1 → 2.1.3
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/.agents/macca-lock.json +1 -1
- package/.agents/skills/_shared/references/brainstorm-session.md +5 -5
- package/.agents/skills/_shared/references/invocation-policy.md +20 -20
- package/.agents/skills/_shared/references/output-ownership.md +11 -11
- package/.agents/skills/_shared/references/scope-rules.md +1 -1
- package/.agents/skills/_shared/references/skill-catalog.md +20 -20
- package/.agents/skills/_shared/scripts/validate-skills.py +37 -15
- package/.agents/skills/add-feature/SKILL.md +9 -3
- package/.agents/skills/antislop-copywriting/SKILL.md +372 -0
- package/.agents/skills/brainstorm-api/SKILL.md +28 -16
- package/.agents/skills/brainstorm-api/assets/api.template.md +35 -15
- package/.agents/skills/brainstorm-architecture/SKILL.md +35 -15
- package/.agents/skills/brainstorm-architecture/assets/architecture.template.md +44 -25
- package/.agents/skills/brainstorm-prd/SKILL.md +47 -17
- package/.agents/skills/brainstorm-prd/assets/PRD.template.md +47 -23
- package/.agents/skills/brainstorm-rules/SKILL.md +36 -19
- package/.agents/skills/brainstorm-rules/assets/rules.template.md +32 -18
- package/.agents/skills/brainstorm-schema/SKILL.md +18 -8
- package/.agents/skills/brainstorm-schema/assets/schema.template.md +25 -10
- package/.agents/skills/brainstorm-styleguide/SKILL.md +37 -19
- package/.agents/skills/brainstorm-styleguide/assets/StyleGuide.template.md +78 -60
- package/.agents/skills/brainstorm-task/SKILL.md +27 -14
- package/.agents/skills/brainstorm-task/assets/Task.template.md +29 -18
- package/.agents/skills/bug-fix/SKILL.md +25 -1
- package/.agents/skills/code-review/SKILL.md +7 -7
- package/.agents/skills/code-review/references/review-checklist.md +21 -10
- package/.agents/skills/developer/SKILL.md +8 -0
- package/.agents/skills/developer/references/execute-task.md +13 -7
- package/.agents/skills/help/SKILL.md +32 -20
- package/.agents/skills/meet/SKILL.md +9 -4
- package/.agents/skills/quick-dev/SKILL.md +27 -22
- package/.agents/skills/release-readiness/SKILL.md +17 -13
- package/.agents/skills/skill-creator/LICENSE.txt +202 -0
- package/.agents/skills/skill-creator/SKILL.md +485 -0
- package/.agents/skills/skill-creator/agents/analyzer.md +274 -0
- package/.agents/skills/skill-creator/agents/comparator.md +202 -0
- package/.agents/skills/skill-creator/agents/grader.md +223 -0
- package/.agents/skills/skill-creator/assets/eval_review.html +146 -0
- package/.agents/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.agents/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.agents/skills/skill-creator/references/schemas.md +441 -0
- package/.agents/skills/skill-creator/scripts/__init__.py +0 -0
- package/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.agents/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.agents/skills/skill-creator/scripts/improve_description.py +247 -0
- package/.agents/skills/skill-creator/scripts/package_skill.py +136 -0
- package/.agents/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/.agents/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.agents/skills/skill-creator/scripts/run_loop.py +328 -0
- package/.agents/skills/skill-creator/scripts/utils.py +47 -0
- package/.agents/skills/spec-audit/SKILL.md +28 -1
- package/.agents/skills/spec-compliance/SKILL.md +31 -18
- package/.agents/skills/spec-init/SKILL.md +29 -17
- package/README.md +158 -122
- package/bin/macca-method.js +1425 -1081
- package/package.json +40 -40
- package/scripts/run-skill-validator.js +27 -9
- package/scripts/test-install.js +600 -357
- package/scripts/test-upgrade-legacy.js +119 -100
- package/scripts/validate-skill-behavior.js +175 -64
package/bin/macca-method.js
CHANGED
|
@@ -11,1335 +11,1679 @@ const readline = require("node:readline");
|
|
|
11
11
|
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
12
12
|
const SOURCE_AGENTS_DIR = path.join(PACKAGE_ROOT, ".agents");
|
|
13
13
|
const SOURCE_SKILLS_DIR = path.join(SOURCE_AGENTS_DIR, "skills");
|
|
14
|
-
const SOURCE_MANAGED_SKILLS_FILE = path.join(
|
|
14
|
+
const SOURCE_MANAGED_SKILLS_FILE = path.join(
|
|
15
|
+
SOURCE_AGENTS_DIR,
|
|
16
|
+
"macca-managed-skills.txt",
|
|
17
|
+
);
|
|
15
18
|
const SOURCE_LOCK_FILE = path.join(SOURCE_AGENTS_DIR, "macca-lock.json");
|
|
16
|
-
const SOURCE_LEGACY_PAYLOADS_FILE = path.join(
|
|
19
|
+
const SOURCE_LEGACY_PAYLOADS_FILE = path.join(
|
|
20
|
+
SOURCE_AGENTS_DIR,
|
|
21
|
+
"legacy-payloads.json",
|
|
22
|
+
);
|
|
17
23
|
const OWNERSHIP_MARKER = ".macca-owned.json";
|
|
18
24
|
const TRANSACTION_FILE = "macca-transaction.json";
|
|
19
25
|
const STATE_FILE = "macca-state.json";
|
|
20
26
|
|
|
21
27
|
const TOOL_DEFINITIONS = [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
28
|
+
{
|
|
29
|
+
key: "copilot",
|
|
30
|
+
aliases: ["github-copilot"],
|
|
31
|
+
label: "GitHub Copilot",
|
|
32
|
+
destination: (targetDir) => path.join(targetDir, ".github", "skills"),
|
|
33
|
+
displayDestination: ".github/skills/",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: "cursor",
|
|
37
|
+
aliases: [],
|
|
38
|
+
label: "Cursor",
|
|
39
|
+
destination: (targetDir) => path.join(targetDir, ".cursor", "skills"),
|
|
40
|
+
displayDestination: ".cursor/skills/",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: "claude",
|
|
44
|
+
aliases: ["claude-code"],
|
|
45
|
+
label: "Claude Code",
|
|
46
|
+
destination: (targetDir) => path.join(targetDir, ".claude", "skills"),
|
|
47
|
+
displayDestination: ".claude/skills/",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: "windsurf",
|
|
51
|
+
aliases: [],
|
|
52
|
+
label: "Windsurf",
|
|
53
|
+
destination: (targetDir) => path.join(targetDir, ".windsurf", "skills"),
|
|
54
|
+
displayDestination: ".windsurf/skills/",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
key: "gemini",
|
|
58
|
+
aliases: ["gemini-cli"],
|
|
59
|
+
label: "Gemini CLI",
|
|
60
|
+
destination: (targetDir) => path.join(targetDir, ".gemini", "skills"),
|
|
61
|
+
displayDestination: ".gemini/skills/",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: "opencode",
|
|
65
|
+
aliases: [],
|
|
66
|
+
label: "OpenCode",
|
|
67
|
+
destination: (targetDir) => path.join(targetDir, ".opencode", "skills"),
|
|
68
|
+
displayDestination: ".opencode/skills/",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: "kilo",
|
|
72
|
+
aliases: ["kilo-code"],
|
|
73
|
+
label: "Kilo Code",
|
|
74
|
+
destination: (targetDir) => path.join(targetDir, ".kilo", "skills"),
|
|
75
|
+
displayDestination: ".kilo/skills/",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: "codex",
|
|
79
|
+
aliases: ["openai-codex"],
|
|
80
|
+
label: "Codex (OpenAI)",
|
|
81
|
+
destination: (targetDir) => path.join(targetDir, ".agents", "skills"),
|
|
82
|
+
displayDestination: ".agents/skills/",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: "kimi",
|
|
86
|
+
aliases: ["kimi-cli"],
|
|
87
|
+
label: "Kimi CLI",
|
|
88
|
+
destination: (targetDir) => path.join(targetDir, ".agents", "skills"),
|
|
89
|
+
displayDestination: ".agents/skills/",
|
|
90
|
+
},
|
|
85
91
|
];
|
|
86
92
|
|
|
87
93
|
const TOOL_BY_KEY = new Map(TOOL_DEFINITIONS.map((tool) => [tool.key, tool]));
|
|
88
94
|
const TOOL_LOOKUP = new Map();
|
|
89
95
|
for (const tool of TOOL_DEFINITIONS) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
TOOL_LOOKUP.set(tool.key, tool.key);
|
|
97
|
+
for (const alias of tool.aliases) {
|
|
98
|
+
TOOL_LOOKUP.set(alias, tool.key);
|
|
99
|
+
}
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
function main() {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (args.version) {
|
|
102
|
-
const packageJson = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
103
|
-
process.stdout.write(`${packageJson.version}\n`);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
103
|
+
try {
|
|
104
|
+
const args = parseArgs(process.argv.slice(2));
|
|
105
|
+
const command = args._[0] || "help";
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
if (args.version) {
|
|
108
|
+
const packageJson = JSON.parse(
|
|
109
|
+
fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8"),
|
|
110
|
+
);
|
|
111
|
+
process.stdout.write(`${packageJson.version}\n`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
if (args.listTools) {
|
|
116
|
+
printToolList();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
116
119
|
|
|
117
|
-
|
|
120
|
+
if (args.help || command === "help") {
|
|
121
|
+
printHelp();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
118
124
|
|
|
119
|
-
|
|
120
|
-
runInstall(args).catch((error) => exitWithError(error.message));
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
125
|
+
ensurePackagedFiles();
|
|
123
126
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
if (command === "install") {
|
|
128
|
+
runInstall(args).catch((error) => exitWithError(error.message));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
128
131
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
+
if (command === "upgrade") {
|
|
133
|
+
runUpgrade(args);
|
|
134
|
+
return;
|
|
132
135
|
}
|
|
136
|
+
|
|
137
|
+
exitWithError(`Unknown command: ${command}`);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
exitWithError(error.message);
|
|
140
|
+
}
|
|
133
141
|
}
|
|
134
142
|
|
|
135
143
|
function parseArgs(argv) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
const args = {
|
|
145
|
+
_: [],
|
|
146
|
+
tools: [],
|
|
147
|
+
};
|
|
140
148
|
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
150
|
+
const token = argv[index];
|
|
143
151
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
if (token === "-h" || token === "--help") {
|
|
153
|
+
args.help = true;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
148
156
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
if (token === "-v" || token === "--version") {
|
|
158
|
+
args.version = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
153
161
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
162
|
+
if (token === "-y" || token === "--yes") {
|
|
163
|
+
args.yes = true;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
158
166
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
if (token === "-f" || token === "--force") {
|
|
168
|
+
args.force = true;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
163
171
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
172
|
+
if (token === "--list-tools") {
|
|
173
|
+
args.listTools = true;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
170
176
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
if (token === "-t" || token === "--tool" || token === "--tools") {
|
|
178
|
+
const result = takeValue(argv, index, token);
|
|
179
|
+
args.tools.push(result.value);
|
|
180
|
+
index = result.index;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
175
183
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
184
|
+
if (token.startsWith("--tool=") || token.startsWith("--tools=")) {
|
|
185
|
+
args.tools.push(token.slice(token.indexOf("=") + 1));
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
182
188
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
if (token === "-d" || token === "--directory") {
|
|
190
|
+
const result = takeValue(argv, index, token);
|
|
191
|
+
args.directory = result.value;
|
|
192
|
+
index = result.index;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
187
195
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
196
|
+
if (token.startsWith("--directory=")) {
|
|
197
|
+
args.directory = token.slice(token.indexOf("=") + 1);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
194
200
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
201
|
+
if (token === "--name") {
|
|
202
|
+
const result = takeValue(argv, index, token);
|
|
203
|
+
args.name = result.value;
|
|
204
|
+
index = result.index;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
199
207
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
208
|
+
if (token.startsWith("--name=")) {
|
|
209
|
+
args.name = token.slice(token.indexOf("=") + 1);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
206
212
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
213
|
+
if (token === "--project") {
|
|
214
|
+
const result = takeValue(argv, index, token);
|
|
215
|
+
args.project = result.value;
|
|
216
|
+
index = result.index;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
211
219
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
220
|
+
if (token.startsWith("--project=")) {
|
|
221
|
+
args.project = token.slice(token.indexOf("=") + 1);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
218
224
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
225
|
+
if (token === "--communication-language") {
|
|
226
|
+
const result = takeValue(argv, index, token);
|
|
227
|
+
args.communicationLanguage = result.value;
|
|
228
|
+
index = result.index;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
223
231
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
232
|
+
if (token.startsWith("--communication-language=")) {
|
|
233
|
+
args.communicationLanguage = token.slice(token.indexOf("=") + 1);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
230
236
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
237
|
+
if (token === "--document-language" || token === "--documents-language") {
|
|
238
|
+
const result = takeValue(argv, index, token);
|
|
239
|
+
args.documentLanguage = result.value;
|
|
240
|
+
index = result.index;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
235
243
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
244
|
+
if (
|
|
245
|
+
token.startsWith("--document-language=") ||
|
|
246
|
+
token.startsWith("--documents-language=")
|
|
247
|
+
) {
|
|
248
|
+
args.documentLanguage = token.slice(token.indexOf("=") + 1);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
239
251
|
|
|
240
|
-
|
|
252
|
+
if (token.startsWith("-")) {
|
|
253
|
+
throw new Error(`Unknown option: ${token}`);
|
|
241
254
|
}
|
|
242
255
|
|
|
243
|
-
|
|
256
|
+
args._.push(token);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return args;
|
|
244
260
|
}
|
|
245
261
|
|
|
246
262
|
function takeValue(argv, index, flag) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
263
|
+
const value = argv[index + 1];
|
|
264
|
+
if (value === undefined) {
|
|
265
|
+
throw new Error(`Missing value for ${flag}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
value,
|
|
270
|
+
index: index + 1,
|
|
271
|
+
};
|
|
256
272
|
}
|
|
257
273
|
|
|
258
274
|
function ensurePackagedFiles() {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
275
|
+
if (!fs.existsSync(SOURCE_SKILLS_DIR)) {
|
|
276
|
+
throw new Error(
|
|
277
|
+
"Packaged skills directory is missing. Reinstall the package or run from the repository root.",
|
|
278
|
+
);
|
|
279
|
+
}
|
|
262
280
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
281
|
+
if (!fs.existsSync(SOURCE_LOCK_FILE)) {
|
|
282
|
+
throw new Error(".agents/macca-lock.json is missing from the package.");
|
|
283
|
+
}
|
|
266
284
|
}
|
|
267
285
|
|
|
268
286
|
function printHelp() {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
)
|
|
287
|
+
process.stdout.write(
|
|
288
|
+
[
|
|
289
|
+
"MACCA CLI",
|
|
290
|
+
"",
|
|
291
|
+
"Usage:",
|
|
292
|
+
" npx macca-method@latest install [options]",
|
|
293
|
+
" npx macca-method@latest upgrade [options]",
|
|
294
|
+
" npx macca-method@latest --list-tools",
|
|
295
|
+
"",
|
|
296
|
+
"Install options:",
|
|
297
|
+
" -t, --tool <name> Repeatable. Also accepts comma-separated values.",
|
|
298
|
+
" -d, --directory <path> Target project directory. Defaults to the current directory.",
|
|
299
|
+
" -y, --yes Skip prompts and use defaults where needed.",
|
|
300
|
+
" -f, --force Overwrite locally modified managed skills.",
|
|
301
|
+
" --name <value> Developer name.",
|
|
302
|
+
" --project <value> Project name.",
|
|
303
|
+
" --communication-language <value>",
|
|
304
|
+
" --document-language <value>",
|
|
305
|
+
"",
|
|
306
|
+
"Examples:",
|
|
307
|
+
" npx macca-method@latest install",
|
|
308
|
+
" npx macca-method@latest install --tool github-copilot --tool codex --yes",
|
|
309
|
+
" npx macca-method@latest upgrade",
|
|
310
|
+
"",
|
|
311
|
+
].join("\n"),
|
|
312
|
+
);
|
|
294
313
|
}
|
|
295
314
|
|
|
296
315
|
function printToolList() {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
316
|
+
process.stdout.write("Supported tools:\n");
|
|
317
|
+
TOOL_DEFINITIONS.forEach((tool, index) => {
|
|
318
|
+
const aliases =
|
|
319
|
+
tool.aliases.length > 0 ? ` (aliases: ${tool.aliases.join(", ")})` : "";
|
|
320
|
+
process.stdout.write(
|
|
321
|
+
` ${index + 1}. ${tool.key} -> ${tool.label}${aliases}\n`,
|
|
322
|
+
);
|
|
323
|
+
});
|
|
302
324
|
}
|
|
303
325
|
|
|
304
326
|
function resolveTargetDirectory(rawDirectory) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
327
|
+
if (!rawDirectory) {
|
|
328
|
+
return process.cwd();
|
|
329
|
+
}
|
|
308
330
|
|
|
309
|
-
|
|
331
|
+
return path.resolve(process.cwd(), rawDirectory);
|
|
310
332
|
}
|
|
311
333
|
|
|
312
334
|
function assertSafeTargetDirectory(rawDirectory) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
335
|
+
const requested = rawDirectory
|
|
336
|
+
? path.resolve(process.cwd(), rawDirectory)
|
|
337
|
+
: process.cwd();
|
|
338
|
+
const absolute = path.isAbsolute(requested)
|
|
339
|
+
? requested
|
|
340
|
+
: path.resolve(requested);
|
|
341
|
+
const segments = absolute.split(path.sep).filter(Boolean);
|
|
342
|
+
let current = absolute.startsWith(path.sep)
|
|
343
|
+
? path.sep
|
|
344
|
+
: segments.shift() || absolute;
|
|
345
|
+
|
|
346
|
+
if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) {
|
|
347
|
+
throw new Error(`Refusing symlinked target project ancestor: ${current}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
for (const segment of segments) {
|
|
351
|
+
current =
|
|
352
|
+
current === path.sep
|
|
353
|
+
? path.join(current, segment)
|
|
354
|
+
: path.join(current, segment);
|
|
318
355
|
if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) {
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
for (const segment of segments) {
|
|
323
|
-
current = current === path.sep ? path.join(current, segment) : path.join(current, segment);
|
|
324
|
-
if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) {
|
|
325
|
-
throw new Error(`Refusing symlinked target project ancestor: ${current}`);
|
|
326
|
-
}
|
|
356
|
+
throw new Error(`Refusing symlinked target project ancestor: ${current}`);
|
|
327
357
|
}
|
|
358
|
+
}
|
|
328
359
|
|
|
329
|
-
|
|
360
|
+
return absolute;
|
|
330
361
|
}
|
|
331
362
|
|
|
332
363
|
function readNonEmptyLines(filePath) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
364
|
+
if (!fs.existsSync(filePath)) {
|
|
365
|
+
return [];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return fs
|
|
369
|
+
.readFileSync(filePath, "utf8")
|
|
370
|
+
.split(/\r?\n/)
|
|
371
|
+
.map((line) => line.trim())
|
|
372
|
+
.filter(Boolean);
|
|
342
373
|
}
|
|
343
374
|
|
|
344
375
|
function unique(values) {
|
|
345
|
-
|
|
376
|
+
return [...new Set(values)];
|
|
346
377
|
}
|
|
347
378
|
|
|
348
379
|
function getSourceManagedSkills() {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
380
|
+
const managedSkills = readNonEmptyLines(SOURCE_MANAGED_SKILLS_FILE);
|
|
381
|
+
if (managedSkills.length > 0) {
|
|
382
|
+
return validateManagedSkillNames(
|
|
383
|
+
managedSkills,
|
|
384
|
+
"packaged managed-skill manifest",
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return validateManagedSkillNames(
|
|
389
|
+
fs
|
|
390
|
+
.readdirSync(SOURCE_SKILLS_DIR, { withFileTypes: true })
|
|
391
|
+
.filter((entry) => entry.isDirectory())
|
|
392
|
+
.map((entry) => entry.name)
|
|
393
|
+
.sort(),
|
|
394
|
+
"packaged skills directory",
|
|
395
|
+
);
|
|
359
396
|
}
|
|
360
397
|
|
|
361
398
|
function validateManagedSkillNames(skillNames, sourceLabel) {
|
|
362
|
-
|
|
399
|
+
const validName = /^(?:_[a-z0-9]+|[a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
363
400
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
401
|
+
for (const skillName of skillNames) {
|
|
402
|
+
if (
|
|
403
|
+
!validName.test(skillName) ||
|
|
404
|
+
skillName.includes("..") ||
|
|
405
|
+
path.basename(skillName) !== skillName
|
|
406
|
+
) {
|
|
407
|
+
throw new Error(`Unsafe skill name in ${sourceLabel}: ${skillName}`);
|
|
368
408
|
}
|
|
409
|
+
}
|
|
369
410
|
|
|
370
|
-
|
|
411
|
+
return unique(skillNames);
|
|
371
412
|
}
|
|
372
413
|
|
|
373
414
|
function resolveOwnedSkillPath(destination, skillName) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
415
|
+
validateManagedSkillNames([skillName], "managed-skill manifest");
|
|
416
|
+
const root = path.resolve(destination);
|
|
417
|
+
const target = path.resolve(root, skillName);
|
|
377
418
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
419
|
+
if (!target.startsWith(`${root}${path.sep}`)) {
|
|
420
|
+
throw new Error(`Refusing path outside skill destination: ${skillName}`);
|
|
421
|
+
}
|
|
381
422
|
|
|
382
|
-
|
|
423
|
+
return target;
|
|
383
424
|
}
|
|
384
425
|
|
|
385
426
|
function normalizeLanguage(value) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
427
|
+
const trimmed = String(value || "").trim();
|
|
428
|
+
const lowered = trimmed.toLowerCase();
|
|
429
|
+
|
|
430
|
+
switch (lowered) {
|
|
431
|
+
case "":
|
|
432
|
+
case "id":
|
|
433
|
+
case "indo":
|
|
434
|
+
case "indonesia":
|
|
435
|
+
case "indonesian":
|
|
436
|
+
case "bahasa indonesia":
|
|
437
|
+
return "indonesian";
|
|
438
|
+
case "en":
|
|
439
|
+
case "eng":
|
|
440
|
+
case "english":
|
|
441
|
+
case "inggris":
|
|
442
|
+
case "bahasa inggris":
|
|
443
|
+
return "english";
|
|
444
|
+
default:
|
|
445
|
+
return lowered;
|
|
446
|
+
}
|
|
406
447
|
}
|
|
407
448
|
|
|
408
449
|
function ensureDirectory(directoryPath) {
|
|
409
|
-
|
|
450
|
+
fs.mkdirSync(directoryPath, { recursive: true });
|
|
410
451
|
}
|
|
411
452
|
|
|
412
453
|
function assertSafeProjectPath(targetDir, destination) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
454
|
+
const targetRoot = path.resolve(targetDir);
|
|
455
|
+
const resolvedDestination = path.resolve(destination);
|
|
456
|
+
if (
|
|
457
|
+
resolvedDestination !== targetRoot &&
|
|
458
|
+
!resolvedDestination.startsWith(`${targetRoot}${path.sep}`)
|
|
459
|
+
) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`Refusing destination outside target project: ${destination}`,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
418
464
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
465
|
+
if (fs.existsSync(targetRoot) && fs.lstatSync(targetRoot).isSymbolicLink()) {
|
|
466
|
+
throw new Error(`Refusing symlinked target project: ${targetRoot}`);
|
|
467
|
+
}
|
|
422
468
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
469
|
+
const relative = path.relative(targetRoot, resolvedDestination);
|
|
470
|
+
let current = targetRoot;
|
|
471
|
+
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
472
|
+
current = path.join(current, segment);
|
|
473
|
+
if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) {
|
|
474
|
+
throw new Error(
|
|
475
|
+
`Refusing symlinked skill destination component: ${current}`,
|
|
476
|
+
);
|
|
430
477
|
}
|
|
478
|
+
}
|
|
431
479
|
}
|
|
432
480
|
|
|
433
481
|
function writeTextFile(filePath, content) {
|
|
434
|
-
|
|
435
|
-
|
|
482
|
+
ensureDirectory(path.dirname(filePath));
|
|
483
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
436
484
|
}
|
|
437
485
|
|
|
438
486
|
function copyFile(sourcePath, targetPath) {
|
|
439
|
-
|
|
440
|
-
|
|
487
|
+
ensureDirectory(path.dirname(targetPath));
|
|
488
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
441
489
|
}
|
|
442
490
|
|
|
443
491
|
function hashFile(filePath) {
|
|
444
|
-
|
|
492
|
+
return crypto
|
|
493
|
+
.createHash("sha256")
|
|
494
|
+
.update(fs.readFileSync(filePath))
|
|
495
|
+
.digest("hex");
|
|
445
496
|
}
|
|
446
497
|
|
|
447
498
|
function hashText(content) {
|
|
448
|
-
|
|
499
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const IGNORED_DIRECTORY_NAMES = new Set([
|
|
503
|
+
OWNERSHIP_MARKER,
|
|
504
|
+
".DS_Store",
|
|
505
|
+
"Thumbs.db",
|
|
506
|
+
"__pycache__",
|
|
507
|
+
".pytest_cache",
|
|
508
|
+
".ruff_cache",
|
|
509
|
+
]);
|
|
510
|
+
|
|
511
|
+
function shouldIgnoreDirectoryEntry(name) {
|
|
512
|
+
if (IGNORED_DIRECTORY_NAMES.has(name)) {
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
if (
|
|
516
|
+
name.endsWith(".pyc") ||
|
|
517
|
+
name.endsWith(".pyo") ||
|
|
518
|
+
name.endsWith(".tmp") ||
|
|
519
|
+
name.startsWith(".tmp-")
|
|
520
|
+
) {
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
449
524
|
}
|
|
450
525
|
|
|
451
526
|
function getDirectoryFileHashes(directoryPath) {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
527
|
+
const hashes = {};
|
|
528
|
+
|
|
529
|
+
function walk(currentPath, relativePath = "") {
|
|
530
|
+
const entries = fs
|
|
531
|
+
.readdirSync(currentPath, { withFileTypes: true })
|
|
532
|
+
.filter((entry) => !shouldIgnoreDirectoryEntry(entry.name))
|
|
533
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
534
|
+
|
|
535
|
+
for (const entry of entries) {
|
|
536
|
+
const entryPath = path.join(currentPath, entry.name);
|
|
537
|
+
const entryRelativePath = path
|
|
538
|
+
.join(relativePath, entry.name)
|
|
539
|
+
.split(path.sep)
|
|
540
|
+
.join("/");
|
|
541
|
+
if (entry.isSymbolicLink()) {
|
|
542
|
+
throw new Error(`Refusing symlink inside managed skill: ${entryPath}`);
|
|
543
|
+
}
|
|
544
|
+
if (entry.isDirectory()) {
|
|
545
|
+
walk(entryPath, entryRelativePath);
|
|
546
|
+
} else if (entry.isFile()) {
|
|
547
|
+
hashes[entryRelativePath] = hashFile(entryPath);
|
|
548
|
+
} else {
|
|
549
|
+
throw new Error(
|
|
550
|
+
`Unsupported file type inside managed skill: ${entryPath}`,
|
|
551
|
+
);
|
|
552
|
+
}
|
|
473
553
|
}
|
|
554
|
+
}
|
|
474
555
|
|
|
475
|
-
|
|
476
|
-
|
|
556
|
+
walk(directoryPath);
|
|
557
|
+
return hashes;
|
|
477
558
|
}
|
|
478
559
|
|
|
479
560
|
function hashDirectory(directoryPath) {
|
|
480
|
-
|
|
561
|
+
return hashText(JSON.stringify(getDirectoryFileHashes(directoryPath)));
|
|
481
562
|
}
|
|
482
563
|
|
|
483
564
|
function getLegacyPayloadHashes() {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
|
|
565
|
+
if (!fs.existsSync(SOURCE_LEGACY_PAYLOADS_FILE)) return {};
|
|
566
|
+
const releases = readJsonObject(
|
|
567
|
+
SOURCE_LEGACY_PAYLOADS_FILE,
|
|
568
|
+
"legacy payload fingerprints",
|
|
569
|
+
);
|
|
570
|
+
const combined = {};
|
|
571
|
+
for (const payloads of Object.values(releases)) {
|
|
572
|
+
if (!payloads || typeof payloads !== "object" || Array.isArray(payloads))
|
|
573
|
+
continue;
|
|
574
|
+
for (const [encodedSkillName, payloadHash] of Object.entries(payloads)) {
|
|
575
|
+
if (
|
|
576
|
+
typeof payloadHash !== "string" ||
|
|
577
|
+
!/^[a-f0-9]{64}$/.test(payloadHash)
|
|
578
|
+
)
|
|
579
|
+
continue;
|
|
580
|
+
const skillName = encodedSkillName.startsWith("base64:")
|
|
581
|
+
? Buffer.from(
|
|
582
|
+
encodedSkillName.slice("base64:".length),
|
|
583
|
+
"base64",
|
|
584
|
+
).toString("utf8")
|
|
585
|
+
: encodedSkillName;
|
|
586
|
+
if (!combined[skillName]) combined[skillName] = new Set();
|
|
587
|
+
combined[skillName].add(payloadHash);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return combined;
|
|
499
591
|
}
|
|
500
592
|
|
|
501
593
|
function isLegacyDirectoryUnchanged(targetPath, skillName) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
594
|
+
if (!fs.existsSync(targetPath) || isMaccaOwned(targetPath)) return false;
|
|
595
|
+
if (!fs.lstatSync(targetPath).isDirectory()) return false;
|
|
596
|
+
const hashes = getLegacyPayloadHashes()[skillName];
|
|
597
|
+
const payloadHash = hashDirectory(targetPath);
|
|
598
|
+
return Boolean(hashes && hashes.has(payloadHash));
|
|
507
599
|
}
|
|
508
600
|
|
|
509
601
|
function buildInstalledLock(managedSkills) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
602
|
+
const lock = JSON.parse(fs.readFileSync(SOURCE_LOCK_FILE, "utf8"));
|
|
603
|
+
lock.hashAlgorithm = "sha256";
|
|
604
|
+
lock.payloadFiles = {};
|
|
605
|
+
for (const skillName of managedSkills) {
|
|
606
|
+
lock.payloadFiles[skillName] = getDirectoryFileHashes(
|
|
607
|
+
path.join(SOURCE_SKILLS_DIR, skillName),
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
return `${JSON.stringify(lock, null, 2)}\n`;
|
|
517
611
|
}
|
|
518
612
|
|
|
519
613
|
function compareSemver(left, right) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
614
|
+
const parse = (value) =>
|
|
615
|
+
String(value || "0.0.0")
|
|
616
|
+
.split("-")[0]
|
|
617
|
+
.split(".")
|
|
618
|
+
.map((part) => Number.parseInt(part, 10) || 0);
|
|
619
|
+
const a = parse(left);
|
|
620
|
+
const b = parse(right);
|
|
621
|
+
const length = Math.max(a.length, b.length);
|
|
622
|
+
for (let index = 0; index < length; index += 1) {
|
|
623
|
+
const diff = (a[index] || 0) - (b[index] || 0);
|
|
624
|
+
if (diff !== 0) return diff;
|
|
625
|
+
}
|
|
626
|
+
return 0;
|
|
532
627
|
}
|
|
533
628
|
|
|
534
629
|
function assertPackageNotOlderThanInstalled(agentsDirectory) {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
630
|
+
const installedLockPath = path.join(agentsDirectory, "macca-lock.json");
|
|
631
|
+
if (!fs.existsSync(installedLockPath)) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const packagedLock = readJsonObject(SOURCE_LOCK_FILE, "packaged MACCA lock");
|
|
636
|
+
const installedLock = readJsonObject(
|
|
637
|
+
installedLockPath,
|
|
638
|
+
"installed MACCA lock",
|
|
639
|
+
);
|
|
640
|
+
const packagedVersion = packagedLock.version;
|
|
641
|
+
const installedVersion = installedLock.version;
|
|
642
|
+
if (
|
|
643
|
+
typeof packagedVersion !== "string" ||
|
|
644
|
+
typeof installedVersion !== "string"
|
|
645
|
+
) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (compareSemver(packagedVersion, installedVersion) < 0) {
|
|
650
|
+
throw new Error(
|
|
651
|
+
`Refusing to downgrade MACCA from ${installedVersion} to ${packagedVersion}. ` +
|
|
652
|
+
"Publish or use a newer package before running install/upgrade.",
|
|
653
|
+
);
|
|
654
|
+
}
|
|
554
655
|
}
|
|
555
656
|
|
|
556
|
-
function readOwnershipMarker(
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
657
|
+
function readOwnershipMarker(
|
|
658
|
+
targetPath,
|
|
659
|
+
expectedSkill = path.basename(targetPath),
|
|
660
|
+
) {
|
|
661
|
+
const markerPath = path.join(targetPath, OWNERSHIP_MARKER);
|
|
662
|
+
if (!fs.existsSync(markerPath)) {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
try {
|
|
667
|
+
const marker = JSON.parse(fs.readFileSync(markerPath, "utf8"));
|
|
668
|
+
if (marker.owner !== "macca-method" || marker.skill !== expectedSkill) {
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
return marker;
|
|
672
|
+
} catch {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
571
675
|
}
|
|
572
676
|
|
|
573
677
|
function isMaccaOwned(targetPath) {
|
|
574
|
-
|
|
678
|
+
return readOwnershipMarker(targetPath) !== null;
|
|
575
679
|
}
|
|
576
680
|
|
|
577
|
-
function assertManagedDirectoryUnchanged(targetPath) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
681
|
+
function assertManagedDirectoryUnchanged(targetPath, force = false) {
|
|
682
|
+
if (force) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
const marker = readOwnershipMarker(targetPath);
|
|
686
|
+
if (!marker) {
|
|
687
|
+
throw new Error(
|
|
688
|
+
`Refusing to overwrite unowned skill directory: ${targetPath}`,
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
if (!marker.payloadHash || marker.payloadHash !== hashDirectory(targetPath)) {
|
|
692
|
+
throw new Error(
|
|
693
|
+
`Refusing to overwrite locally modified managed skill: ${targetPath}. ` +
|
|
694
|
+
"Run upgrade with --force to overwrite local modifications.",
|
|
695
|
+
);
|
|
696
|
+
}
|
|
585
697
|
}
|
|
586
698
|
|
|
587
699
|
function readJsonObject(filePath, label) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
700
|
+
try {
|
|
701
|
+
const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
702
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
703
|
+
throw new Error("root value must be an object");
|
|
704
|
+
}
|
|
705
|
+
return value;
|
|
706
|
+
} catch (error) {
|
|
707
|
+
throw new Error(`Invalid ${label} at ${filePath}: ${error.message}`);
|
|
708
|
+
}
|
|
597
709
|
}
|
|
598
710
|
|
|
599
|
-
function assertManagedFilesUnchanged(agentsDirectory) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
711
|
+
function assertManagedFilesUnchanged(agentsDirectory, force = false) {
|
|
712
|
+
if (force) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const statePath = path.join(agentsDirectory, STATE_FILE);
|
|
716
|
+
if (!fs.existsSync(statePath)) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const state = readJsonObject(statePath, "MACCA state");
|
|
720
|
+
for (const [fileName, expectedHash] of Object.entries(state.files || {})) {
|
|
721
|
+
const filePath = path.join(agentsDirectory, fileName);
|
|
722
|
+
if (!fs.existsSync(filePath) || hashFile(filePath) !== expectedHash) {
|
|
723
|
+
throw new Error(
|
|
724
|
+
`Refusing to overwrite locally modified MACCA metadata: ${filePath}. ` +
|
|
725
|
+
"Run upgrade with --force to overwrite local modifications.",
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
611
729
|
}
|
|
612
730
|
|
|
613
731
|
function buildStateContent(files) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
732
|
+
const hashes = {};
|
|
733
|
+
for (const [fileName, content] of Object.entries(files)) {
|
|
734
|
+
hashes[fileName] = hashText(content);
|
|
735
|
+
}
|
|
736
|
+
return `${JSON.stringify({ version: 1, hashAlgorithm: "sha256", files: hashes }, null, 2)}\n`;
|
|
619
737
|
}
|
|
620
738
|
|
|
621
739
|
function writeJsonAtomic(filePath, value) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
740
|
+
const temporaryPath = `${filePath}.tmp-${process.pid}`;
|
|
741
|
+
fs.writeFileSync(
|
|
742
|
+
temporaryPath,
|
|
743
|
+
`${JSON.stringify(value, null, 2)}\n`,
|
|
744
|
+
"utf8",
|
|
745
|
+
);
|
|
746
|
+
fs.renameSync(temporaryPath, filePath);
|
|
625
747
|
}
|
|
626
748
|
|
|
627
749
|
function removeManagedPath(targetPath, kind) {
|
|
628
|
-
|
|
750
|
+
fs.rmSync(targetPath, { recursive: kind === "directory", force: true });
|
|
629
751
|
}
|
|
630
752
|
|
|
631
753
|
function assertTransactionPayload(entry, transactionId, candidatePath) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
754
|
+
if (!fs.existsSync(candidatePath)) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (entry.kind === "directory") {
|
|
758
|
+
const marker = readOwnershipMarker(
|
|
759
|
+
candidatePath,
|
|
760
|
+
path.basename(entry.targetPath),
|
|
761
|
+
);
|
|
762
|
+
if (!marker || marker.transactionId !== transactionId) {
|
|
763
|
+
throw new Error(
|
|
764
|
+
`Refusing recovery of directory without matching transaction evidence: ${candidatePath}`,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (!entry.stagedHash || hashFile(candidatePath) !== entry.stagedHash) {
|
|
770
|
+
throw new Error(
|
|
771
|
+
`Refusing recovery of file without matching transaction hash: ${candidatePath}`,
|
|
772
|
+
);
|
|
773
|
+
}
|
|
645
774
|
}
|
|
646
775
|
|
|
647
776
|
function assertTransactionBackup(entry) {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
777
|
+
if (!fs.existsSync(entry.backupPath)) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
if (entry.kind === "directory") {
|
|
781
|
+
const marker = readOwnershipMarker(
|
|
782
|
+
entry.backupPath,
|
|
783
|
+
path.basename(entry.targetPath),
|
|
784
|
+
);
|
|
785
|
+
const validManaged =
|
|
786
|
+
marker &&
|
|
787
|
+
marker.payloadHash &&
|
|
788
|
+
marker.payloadHash === hashDirectory(entry.backupPath);
|
|
789
|
+
const validLegacy =
|
|
790
|
+
entry.legacySkillName &&
|
|
791
|
+
isLegacyDirectoryUnchanged(entry.backupPath, entry.legacySkillName);
|
|
792
|
+
if (!validManaged && !validLegacy) {
|
|
793
|
+
throw new Error(
|
|
794
|
+
`Refusing recovery from unowned or modified backup directory: ${entry.backupPath}`,
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
660
798
|
}
|
|
661
799
|
|
|
662
800
|
function validateTransactionEntries(targetDir, journal) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
801
|
+
if (
|
|
802
|
+
journal.version !== 1 ||
|
|
803
|
+
!["committing", "committed"].includes(journal.phase)
|
|
804
|
+
) {
|
|
805
|
+
throw new Error("Unsupported MACCA transaction journal version or phase");
|
|
806
|
+
}
|
|
807
|
+
if (
|
|
808
|
+
typeof journal.transactionId !== "string" ||
|
|
809
|
+
!/^\d+-\d+-[a-f0-9]+$/.test(journal.transactionId)
|
|
810
|
+
) {
|
|
811
|
+
throw new Error("Invalid MACCA transaction ID");
|
|
812
|
+
}
|
|
813
|
+
if (!Array.isArray(journal.entries)) {
|
|
814
|
+
throw new Error("Invalid MACCA transaction journal entries");
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const metadataTargets = new Set(
|
|
818
|
+
[
|
|
819
|
+
"macca-managed-skills.txt",
|
|
820
|
+
"macca-lock.json",
|
|
821
|
+
"macca-tools.txt",
|
|
822
|
+
"developer-config.json",
|
|
823
|
+
STATE_FILE,
|
|
824
|
+
].map((fileName) => path.resolve(targetDir, ".agents", fileName)),
|
|
825
|
+
);
|
|
826
|
+
const skillParents = new Set([
|
|
827
|
+
...TOOL_DEFINITIONS.map((tool) =>
|
|
828
|
+
path.resolve(tool.destination(targetDir)),
|
|
829
|
+
),
|
|
830
|
+
path.resolve(targetDir, ".opencode", "skill"),
|
|
831
|
+
]);
|
|
832
|
+
const seenPaths = new Set();
|
|
833
|
+
|
|
834
|
+
for (const entry of journal.entries) {
|
|
835
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
836
|
+
throw new Error("Invalid MACCA transaction journal entry");
|
|
837
|
+
}
|
|
838
|
+
if (entry.kind !== "directory" && entry.kind !== "file") {
|
|
839
|
+
throw new Error(`Invalid transaction entry kind: ${entry.kind}`);
|
|
840
|
+
}
|
|
841
|
+
if (
|
|
842
|
+
typeof entry.targetPath !== "string" ||
|
|
843
|
+
typeof entry.backupPath !== "string"
|
|
844
|
+
) {
|
|
845
|
+
throw new Error("Transaction entry paths must be strings");
|
|
846
|
+
}
|
|
847
|
+
if (entry.stagingPath !== null && typeof entry.stagingPath !== "string") {
|
|
848
|
+
throw new Error("Transaction staging path must be a string or null");
|
|
849
|
+
}
|
|
850
|
+
if (typeof entry.hadTarget !== "boolean") {
|
|
851
|
+
throw new Error("Transaction hadTarget must be boolean");
|
|
852
|
+
}
|
|
853
|
+
if (
|
|
854
|
+
entry.legacySkillName !== undefined &&
|
|
855
|
+
(typeof entry.legacySkillName !== "string" ||
|
|
856
|
+
!/^(?:_[a-z0-9]+|[a-z0-9]+(?:-[a-z0-9]+)*)$/.test(
|
|
857
|
+
entry.legacySkillName,
|
|
858
|
+
))
|
|
859
|
+
) {
|
|
860
|
+
throw new Error("Transaction legacy skill name is invalid");
|
|
861
|
+
}
|
|
862
|
+
if (
|
|
863
|
+
entry.stagingPath !== null &&
|
|
864
|
+
(typeof entry.stagedHash !== "string" ||
|
|
865
|
+
!/^[a-f0-9]{64}$/.test(entry.stagedHash))
|
|
866
|
+
) {
|
|
867
|
+
throw new Error("Transaction staged payload hash is missing or invalid");
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const targetPath = path.resolve(entry.targetPath);
|
|
871
|
+
const parent = path.dirname(targetPath);
|
|
872
|
+
const targetName = path.basename(targetPath);
|
|
873
|
+
const isValidFile =
|
|
874
|
+
entry.kind === "file" && metadataTargets.has(targetPath);
|
|
875
|
+
const isValidSkill =
|
|
876
|
+
entry.kind === "directory" &&
|
|
877
|
+
skillParents.has(parent) &&
|
|
878
|
+
/^(?:_[a-z0-9]+|[a-z0-9]+(?:-[a-z0-9]+)*)$/.test(targetName);
|
|
879
|
+
if (!isValidFile && !isValidSkill) {
|
|
880
|
+
throw new Error(
|
|
881
|
+
`Transaction target is not a managed MACCA path: ${targetPath}`,
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const backupPath = path.resolve(entry.backupPath);
|
|
886
|
+
const expectedBackupPath = path.join(
|
|
887
|
+
parent,
|
|
888
|
+
`.${targetName}.macca-backup-${journal.transactionId}`,
|
|
889
|
+
);
|
|
890
|
+
if (backupPath !== expectedBackupPath) {
|
|
891
|
+
throw new Error(`Invalid MACCA transaction backup path: ${backupPath}`);
|
|
892
|
+
}
|
|
893
|
+
if (entry.stagingPath !== null) {
|
|
894
|
+
const stagingPath = path.resolve(entry.stagingPath);
|
|
895
|
+
const expectedStagingPath = path.join(
|
|
896
|
+
parent,
|
|
897
|
+
`.${targetName}.macca-stage-${journal.transactionId}`,
|
|
898
|
+
);
|
|
899
|
+
if (stagingPath !== expectedStagingPath) {
|
|
900
|
+
throw new Error(
|
|
901
|
+
`Invalid MACCA transaction staging path: ${stagingPath}`,
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
735
905
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
906
|
+
for (const candidate of [
|
|
907
|
+
targetPath,
|
|
908
|
+
backupPath,
|
|
909
|
+
entry.stagingPath && path.resolve(entry.stagingPath),
|
|
910
|
+
].filter(Boolean)) {
|
|
911
|
+
assertSafeProjectPath(targetDir, candidate);
|
|
912
|
+
if (seenPaths.has(candidate)) {
|
|
913
|
+
throw new Error(
|
|
914
|
+
`Duplicate path in MACCA transaction journal: ${candidate}`,
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
seenPaths.add(candidate);
|
|
743
918
|
}
|
|
919
|
+
}
|
|
744
920
|
|
|
745
|
-
|
|
921
|
+
return journal.entries;
|
|
746
922
|
}
|
|
747
923
|
|
|
748
924
|
function recoverInterruptedTransaction(targetDir) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
925
|
+
const journalPath = path.join(targetDir, ".agents", TRANSACTION_FILE);
|
|
926
|
+
assertSafeProjectPath(targetDir, journalPath);
|
|
927
|
+
if (!fs.existsSync(journalPath)) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const journal = readJsonObject(journalPath, "MACCA transaction journal");
|
|
932
|
+
const entries = validateTransactionEntries(targetDir, journal);
|
|
933
|
+
for (const entry of entries) {
|
|
934
|
+
assertTransactionBackup(entry);
|
|
935
|
+
if (
|
|
936
|
+
journal.phase === "committed" ||
|
|
937
|
+
fs.existsSync(entry.backupPath) ||
|
|
938
|
+
!entry.hadTarget
|
|
939
|
+
) {
|
|
940
|
+
assertTransactionPayload(entry, journal.transactionId, entry.targetPath);
|
|
941
|
+
}
|
|
942
|
+
if (entry.stagingPath && fs.existsSync(entry.stagingPath)) {
|
|
943
|
+
assertTransactionPayload(entry, journal.transactionId, entry.stagingPath);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (journal.phase === "committed") {
|
|
757
947
|
for (const entry of entries) {
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
if (fs.existsSync(entry.backupPath)) {
|
|
774
|
-
removeManagedPath(entry.targetPath, entry.kind);
|
|
775
|
-
fs.renameSync(entry.backupPath, entry.targetPath);
|
|
776
|
-
} else if (!entry.hadTarget) {
|
|
777
|
-
removeManagedPath(entry.targetPath, entry.kind);
|
|
778
|
-
}
|
|
779
|
-
if (entry.stagingPath) removeManagedPath(entry.stagingPath, entry.kind);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
fs.rmSync(journalPath, { force: true });
|
|
948
|
+
removeManagedPath(entry.backupPath, entry.kind);
|
|
949
|
+
if (entry.stagingPath) removeManagedPath(entry.stagingPath, entry.kind);
|
|
950
|
+
}
|
|
951
|
+
} else {
|
|
952
|
+
for (const entry of [...entries].reverse()) {
|
|
953
|
+
if (fs.existsSync(entry.backupPath)) {
|
|
954
|
+
removeManagedPath(entry.targetPath, entry.kind);
|
|
955
|
+
fs.renameSync(entry.backupPath, entry.targetPath);
|
|
956
|
+
} else if (!entry.hadTarget) {
|
|
957
|
+
removeManagedPath(entry.targetPath, entry.kind);
|
|
958
|
+
}
|
|
959
|
+
if (entry.stagingPath) removeManagedPath(entry.stagingPath, entry.kind);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
fs.rmSync(journalPath, { force: true });
|
|
783
963
|
}
|
|
784
964
|
|
|
785
965
|
function getUniqueDestinations(targetDir, tools) {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
966
|
+
const destinations = new Map();
|
|
967
|
+
for (const toolKey of tools) {
|
|
968
|
+
const tool = TOOL_BY_KEY.get(toolKey);
|
|
969
|
+
if (!tool) {
|
|
970
|
+
throw new Error(`Unsupported tool: ${toolKey}`);
|
|
971
|
+
}
|
|
972
|
+
const destination = tool.destination(targetDir);
|
|
973
|
+
assertSafeProjectPath(targetDir, destination);
|
|
974
|
+
destinations.set(path.resolve(destination), destination);
|
|
975
|
+
}
|
|
976
|
+
return [...destinations.values()];
|
|
797
977
|
}
|
|
798
978
|
|
|
799
|
-
function applySkillTransaction(entries, targetDir) {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
979
|
+
function applySkillTransaction(entries, targetDir, force = false) {
|
|
980
|
+
const suffix = `${process.pid}-${Date.now()}-${crypto.randomBytes(8).toString("hex")}`;
|
|
981
|
+
const prepared = [];
|
|
982
|
+
const committed = [];
|
|
983
|
+
const journalPath = path.join(targetDir, ".agents", TRANSACTION_FILE);
|
|
984
|
+
|
|
985
|
+
for (const entry of entries) {
|
|
986
|
+
const {
|
|
987
|
+
targetPath,
|
|
988
|
+
sourcePath,
|
|
989
|
+
content,
|
|
990
|
+
kind = "directory",
|
|
991
|
+
legacySkillName,
|
|
992
|
+
} = entry;
|
|
993
|
+
const targetName = path.basename(targetPath);
|
|
994
|
+
const parent = path.dirname(targetPath);
|
|
995
|
+
if (fs.existsSync(targetPath)) {
|
|
996
|
+
const stat = fs.lstatSync(targetPath);
|
|
997
|
+
if (stat.isSymbolicLink()) {
|
|
998
|
+
throw new Error(
|
|
999
|
+
`Refusing to replace symlinked managed path: ${targetPath}`,
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
if (kind === "directory" && !stat.isDirectory()) {
|
|
1003
|
+
throw new Error(`Skill destination is not a directory: ${targetPath}`);
|
|
1004
|
+
}
|
|
1005
|
+
if (kind === "file" && !stat.isFile()) {
|
|
1006
|
+
throw new Error(
|
|
1007
|
+
`Managed file destination is not a file: ${targetPath}`,
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
if (kind === "directory") {
|
|
1011
|
+
if (isMaccaOwned(targetPath)) {
|
|
1012
|
+
assertManagedDirectoryUnchanged(targetPath, force);
|
|
1013
|
+
} else if (
|
|
1014
|
+
!legacySkillName ||
|
|
1015
|
+
!isLegacyDirectoryUnchanged(targetPath, legacySkillName)
|
|
1016
|
+
) {
|
|
1017
|
+
throw new Error(
|
|
1018
|
+
`Refusing to overwrite or remove unowned skill directory: ${targetPath}`,
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
prepared.push({
|
|
1024
|
+
...entry,
|
|
1025
|
+
kind,
|
|
1026
|
+
hadTarget: fs.existsSync(targetPath),
|
|
1027
|
+
stagingPath:
|
|
1028
|
+
sourcePath || content !== undefined
|
|
1029
|
+
? path.join(parent, `.${targetName}.macca-stage-${suffix}`)
|
|
1030
|
+
: null,
|
|
1031
|
+
backupPath: path.join(parent, `.${targetName}.macca-backup-${suffix}`),
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
try {
|
|
1036
|
+
for (const entry of prepared) {
|
|
1037
|
+
ensureDirectory(path.dirname(entry.targetPath));
|
|
1038
|
+
if (!entry.sourcePath && entry.content === undefined) {
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (entry.kind === "file") {
|
|
1042
|
+
if (entry.sourcePath) {
|
|
1043
|
+
fs.copyFileSync(entry.sourcePath, entry.stagingPath);
|
|
1044
|
+
} else {
|
|
1045
|
+
fs.writeFileSync(entry.stagingPath, entry.content, "utf8");
|
|
1046
|
+
}
|
|
1047
|
+
} else {
|
|
1048
|
+
fs.cpSync(entry.sourcePath, entry.stagingPath, { recursive: true });
|
|
1049
|
+
const payloadHash = hashDirectory(entry.stagingPath);
|
|
1050
|
+
writeTextFile(
|
|
1051
|
+
path.join(entry.stagingPath, OWNERSHIP_MARKER),
|
|
1052
|
+
`${JSON.stringify(
|
|
1053
|
+
{
|
|
1054
|
+
owner: "macca-method",
|
|
1055
|
+
skill: path.basename(entry.targetPath),
|
|
1056
|
+
hashAlgorithm: "sha256",
|
|
1057
|
+
payloadHash,
|
|
1058
|
+
transactionId: suffix,
|
|
1059
|
+
},
|
|
1060
|
+
null,
|
|
1061
|
+
2,
|
|
1062
|
+
)}\n`,
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
entry.stagedHash =
|
|
1066
|
+
entry.kind === "file"
|
|
1067
|
+
? hashFile(entry.stagingPath)
|
|
1068
|
+
: hashDirectory(entry.stagingPath);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
writeJsonAtomic(journalPath, {
|
|
1072
|
+
version: 1,
|
|
1073
|
+
phase: "committing",
|
|
1074
|
+
transactionId: suffix,
|
|
1075
|
+
entries: prepared.map(
|
|
1076
|
+
({
|
|
1077
|
+
targetPath,
|
|
1078
|
+
stagingPath,
|
|
1079
|
+
backupPath,
|
|
1080
|
+
kind,
|
|
1081
|
+
hadTarget,
|
|
1082
|
+
stagedHash,
|
|
1083
|
+
legacySkillName,
|
|
1084
|
+
}) => ({
|
|
1085
|
+
targetPath,
|
|
1086
|
+
stagingPath,
|
|
1087
|
+
backupPath,
|
|
1088
|
+
kind,
|
|
1089
|
+
hadTarget,
|
|
1090
|
+
stagedHash: stagingPath ? stagedHash : null,
|
|
1091
|
+
legacySkillName,
|
|
1092
|
+
}),
|
|
1093
|
+
),
|
|
1094
|
+
});
|
|
804
1095
|
|
|
805
|
-
for (const entry of
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1096
|
+
for (const entry of prepared) {
|
|
1097
|
+
if (entry.hadTarget) {
|
|
1098
|
+
fs.renameSync(entry.targetPath, entry.backupPath);
|
|
1099
|
+
}
|
|
1100
|
+
committed.push(entry);
|
|
1101
|
+
if (entry.sourcePath || entry.content !== undefined) {
|
|
1102
|
+
fs.renameSync(entry.stagingPath, entry.targetPath);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
writeJsonAtomic(journalPath, {
|
|
1106
|
+
version: 1,
|
|
1107
|
+
phase: "committed",
|
|
1108
|
+
transactionId: suffix,
|
|
1109
|
+
entries: prepared.map(
|
|
1110
|
+
({
|
|
1111
|
+
targetPath,
|
|
1112
|
+
stagingPath,
|
|
1113
|
+
backupPath,
|
|
1114
|
+
kind,
|
|
1115
|
+
hadTarget,
|
|
1116
|
+
stagedHash,
|
|
1117
|
+
legacySkillName,
|
|
1118
|
+
}) => ({
|
|
1119
|
+
targetPath,
|
|
1120
|
+
stagingPath,
|
|
1121
|
+
backupPath,
|
|
1122
|
+
kind,
|
|
1123
|
+
hadTarget,
|
|
1124
|
+
stagedHash: stagingPath ? stagedHash : null,
|
|
1125
|
+
legacySkillName,
|
|
1126
|
+
}),
|
|
1127
|
+
),
|
|
1128
|
+
});
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
for (const entry of [...committed].reverse()) {
|
|
1131
|
+
fs.rmSync(entry.targetPath, {
|
|
1132
|
+
recursive: entry.kind === "directory",
|
|
1133
|
+
force: true,
|
|
1134
|
+
});
|
|
1135
|
+
if (entry.hadTarget && fs.existsSync(entry.backupPath)) {
|
|
1136
|
+
fs.renameSync(entry.backupPath, entry.targetPath);
|
|
1137
|
+
}
|
|
837
1138
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
if (entry.sourcePath) {
|
|
847
|
-
fs.copyFileSync(entry.sourcePath, entry.stagingPath);
|
|
848
|
-
} else {
|
|
849
|
-
fs.writeFileSync(entry.stagingPath, entry.content, "utf8");
|
|
850
|
-
}
|
|
851
|
-
} else {
|
|
852
|
-
fs.cpSync(entry.sourcePath, entry.stagingPath, { recursive: true });
|
|
853
|
-
const payloadHash = hashDirectory(entry.stagingPath);
|
|
854
|
-
writeTextFile(
|
|
855
|
-
path.join(entry.stagingPath, OWNERSHIP_MARKER),
|
|
856
|
-
`${JSON.stringify({
|
|
857
|
-
owner: "macca-method",
|
|
858
|
-
skill: path.basename(entry.targetPath),
|
|
859
|
-
hashAlgorithm: "sha256",
|
|
860
|
-
payloadHash,
|
|
861
|
-
transactionId: suffix
|
|
862
|
-
}, null, 2)}\n`
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
|
-
entry.stagedHash = entry.kind === "file"
|
|
866
|
-
? hashFile(entry.stagingPath)
|
|
867
|
-
: hashDirectory(entry.stagingPath);
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
writeJsonAtomic(journalPath, {
|
|
871
|
-
version: 1,
|
|
872
|
-
phase: "committing",
|
|
873
|
-
transactionId: suffix,
|
|
874
|
-
entries: prepared.map(({ targetPath, stagingPath, backupPath, kind, hadTarget, stagedHash, legacySkillName }) => ({
|
|
875
|
-
targetPath,
|
|
876
|
-
stagingPath,
|
|
877
|
-
backupPath,
|
|
878
|
-
kind,
|
|
879
|
-
hadTarget,
|
|
880
|
-
stagedHash: stagingPath ? stagedHash : null,
|
|
881
|
-
legacySkillName
|
|
882
|
-
}))
|
|
883
|
-
});
|
|
884
|
-
|
|
885
|
-
for (const entry of prepared) {
|
|
886
|
-
if (entry.hadTarget) {
|
|
887
|
-
fs.renameSync(entry.targetPath, entry.backupPath);
|
|
888
|
-
}
|
|
889
|
-
committed.push(entry);
|
|
890
|
-
if (entry.sourcePath || entry.content !== undefined) {
|
|
891
|
-
fs.renameSync(entry.stagingPath, entry.targetPath);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
writeJsonAtomic(journalPath, {
|
|
895
|
-
version: 1,
|
|
896
|
-
phase: "committed",
|
|
897
|
-
transactionId: suffix,
|
|
898
|
-
entries: prepared.map(({ targetPath, stagingPath, backupPath, kind, hadTarget, stagedHash, legacySkillName }) => ({
|
|
899
|
-
targetPath,
|
|
900
|
-
stagingPath,
|
|
901
|
-
backupPath,
|
|
902
|
-
kind,
|
|
903
|
-
hadTarget,
|
|
904
|
-
stagedHash: stagingPath ? stagedHash : null,
|
|
905
|
-
legacySkillName
|
|
906
|
-
}))
|
|
1139
|
+
fs.rmSync(journalPath, { force: true });
|
|
1140
|
+
throw error;
|
|
1141
|
+
} finally {
|
|
1142
|
+
for (const entry of prepared) {
|
|
1143
|
+
if (entry.stagingPath) {
|
|
1144
|
+
fs.rmSync(entry.stagingPath, {
|
|
1145
|
+
recursive: entry.kind === "directory",
|
|
1146
|
+
force: true,
|
|
907
1147
|
});
|
|
908
|
-
|
|
909
|
-
for (const entry of [...committed].reverse()) {
|
|
910
|
-
fs.rmSync(entry.targetPath, { recursive: entry.kind === "directory", force: true });
|
|
911
|
-
if (entry.hadTarget && fs.existsSync(entry.backupPath)) {
|
|
912
|
-
fs.renameSync(entry.backupPath, entry.targetPath);
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
fs.rmSync(journalPath, { force: true });
|
|
916
|
-
throw error;
|
|
917
|
-
} finally {
|
|
918
|
-
for (const entry of prepared) {
|
|
919
|
-
if (entry.stagingPath) {
|
|
920
|
-
fs.rmSync(entry.stagingPath, { recursive: entry.kind === "directory", force: true });
|
|
921
|
-
}
|
|
922
|
-
}
|
|
1148
|
+
}
|
|
923
1149
|
}
|
|
1150
|
+
}
|
|
924
1151
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1152
|
+
for (const entry of committed) {
|
|
1153
|
+
fs.rmSync(entry.backupPath, {
|
|
1154
|
+
recursive: entry.kind === "directory",
|
|
1155
|
+
force: true,
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
fs.rmSync(journalPath, { force: true });
|
|
929
1159
|
}
|
|
930
1160
|
|
|
931
1161
|
function normalizeToolList(values) {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1162
|
+
const rawTokens = [];
|
|
1163
|
+
for (const value of values) {
|
|
1164
|
+
rawTokens.push(...String(value).split(","));
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
const resolved = [];
|
|
1168
|
+
for (const token of rawTokens) {
|
|
1169
|
+
const trimmed = token.trim().toLowerCase();
|
|
1170
|
+
if (!trimmed) {
|
|
1171
|
+
continue;
|
|
935
1172
|
}
|
|
936
1173
|
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if (!trimmed) {
|
|
941
|
-
continue;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
const key = TOOL_LOOKUP.get(trimmed);
|
|
945
|
-
if (!key) {
|
|
946
|
-
throw new Error(`Unknown tool: ${token}`);
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
resolved.push(key);
|
|
1174
|
+
const key = TOOL_LOOKUP.get(trimmed);
|
|
1175
|
+
if (!key) {
|
|
1176
|
+
throw new Error(`Unknown tool: ${token}`);
|
|
950
1177
|
}
|
|
951
1178
|
|
|
952
|
-
|
|
1179
|
+
resolved.push(key);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
return unique(resolved);
|
|
953
1183
|
}
|
|
954
1184
|
|
|
955
1185
|
function buildDeveloperConfig(options) {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1186
|
+
const config = {};
|
|
1187
|
+
if (options.name !== undefined) config.name = options.name;
|
|
1188
|
+
if (options.project !== undefined) config.project = options.project;
|
|
1189
|
+
if (
|
|
1190
|
+
options.communicationLanguage !== undefined ||
|
|
1191
|
+
options.documentLanguage !== undefined
|
|
1192
|
+
) {
|
|
1193
|
+
config.languagePreferences = {};
|
|
1194
|
+
if (options.communicationLanguage !== undefined) {
|
|
1195
|
+
config.languagePreferences.communication = {
|
|
1196
|
+
raw: options.communicationLanguage,
|
|
1197
|
+
normalized: normalizeLanguage(options.communicationLanguage),
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
if (options.documentLanguage !== undefined) {
|
|
1201
|
+
config.languagePreferences.documents = {
|
|
1202
|
+
raw: options.documentLanguage,
|
|
1203
|
+
normalized: normalizeLanguage(options.documentLanguage),
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return config;
|
|
975
1208
|
}
|
|
976
1209
|
|
|
977
1210
|
function mergeObjects(existing, update) {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
}
|
|
1211
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
1212
|
+
return update;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
const merged = { ...existing };
|
|
1216
|
+
for (const [key, value] of Object.entries(update)) {
|
|
1217
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1218
|
+
merged[key] = mergeObjects(existing[key], value);
|
|
1219
|
+
} else {
|
|
1220
|
+
merged[key] = value;
|
|
989
1221
|
}
|
|
990
|
-
|
|
1222
|
+
}
|
|
1223
|
+
return merged;
|
|
991
1224
|
}
|
|
992
1225
|
|
|
993
1226
|
function readDeveloperConfig(filePath) {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1227
|
+
if (!fs.existsSync(filePath)) {
|
|
1228
|
+
return {};
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
try {
|
|
1232
|
+
const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
1233
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1234
|
+
throw new Error("root value must be an object");
|
|
1235
|
+
}
|
|
1236
|
+
return value;
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
throw new Error(`Cannot preserve malformed ${filePath}: ${error.message}`);
|
|
1239
|
+
}
|
|
1007
1240
|
}
|
|
1008
1241
|
|
|
1009
1242
|
function createPrompt() {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1243
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1244
|
+
throw new Error(
|
|
1245
|
+
"Interactive install requires a TTY. Use --yes and explicit flags in non-interactive environments.",
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1013
1248
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1249
|
+
return readline.createInterface({
|
|
1250
|
+
input: process.stdin,
|
|
1251
|
+
output: process.stdout,
|
|
1252
|
+
});
|
|
1018
1253
|
}
|
|
1019
1254
|
|
|
1020
1255
|
function askQuestion(prompt, question) {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1256
|
+
return new Promise((resolve) => {
|
|
1257
|
+
prompt.question(question, resolve);
|
|
1258
|
+
});
|
|
1024
1259
|
}
|
|
1025
1260
|
|
|
1026
1261
|
async function promptForTools() {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
try {
|
|
1030
|
-
process.stdout.write("\nPilih AI tool yang mau dipasang:\n");
|
|
1031
|
-
TOOL_DEFINITIONS.forEach((tool, index) => {
|
|
1032
|
-
process.stdout.write(` ${index + 1}. ${tool.label} -> ${tool.displayDestination}\n`);
|
|
1033
|
-
});
|
|
1034
|
-
process.stdout.write("\n");
|
|
1262
|
+
const prompt = createPrompt();
|
|
1035
1263
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1264
|
+
try {
|
|
1265
|
+
process.stdout.write("\nPilih AI tool yang mau dipasang:\n");
|
|
1266
|
+
TOOL_DEFINITIONS.forEach((tool, index) => {
|
|
1267
|
+
process.stdout.write(
|
|
1268
|
+
` ${index + 1}. ${tool.label} -> ${tool.displayDestination}\n`,
|
|
1269
|
+
);
|
|
1270
|
+
});
|
|
1271
|
+
process.stdout.write("\n");
|
|
1043
1272
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1273
|
+
while (true) {
|
|
1274
|
+
const answer = await askQuestion(
|
|
1275
|
+
prompt,
|
|
1276
|
+
"Masukkan nomor/nama tool (pisahkan dengan koma, 'all', kosong = codex): ",
|
|
1277
|
+
);
|
|
1278
|
+
const parsed = parseInteractiveToolSelection(answer);
|
|
1279
|
+
if (parsed.error) {
|
|
1280
|
+
process.stdout.write(` ${parsed.error}\n`);
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return parsed.tools;
|
|
1285
|
+
}
|
|
1286
|
+
} finally {
|
|
1287
|
+
prompt.close();
|
|
1288
|
+
}
|
|
1049
1289
|
}
|
|
1050
1290
|
|
|
1051
1291
|
function parseInteractiveToolSelection(answer) {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1292
|
+
const trimmed = String(answer || "").trim();
|
|
1293
|
+
if (!trimmed) {
|
|
1294
|
+
return { tools: ["codex"] };
|
|
1295
|
+
}
|
|
1056
1296
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1297
|
+
if (trimmed.toLowerCase() === "all") {
|
|
1298
|
+
return { tools: TOOL_DEFINITIONS.map((tool) => tool.key) };
|
|
1299
|
+
}
|
|
1060
1300
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1301
|
+
const tools = [];
|
|
1302
|
+
for (const part of trimmed.split(",")) {
|
|
1303
|
+
const token = part.trim();
|
|
1304
|
+
if (!token) {
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1067
1307
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1308
|
+
if (/^\d+$/.test(token)) {
|
|
1309
|
+
const index = Number(token) - 1;
|
|
1310
|
+
if (index < 0 || index >= TOOL_DEFINITIONS.length) {
|
|
1311
|
+
return { error: `Pilihan nomor tidak valid: ${token}` };
|
|
1312
|
+
}
|
|
1073
1313
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
const key = TOOL_LOOKUP.get(token.toLowerCase());
|
|
1079
|
-
if (!key) {
|
|
1080
|
-
return { error: `Nama tool tidak dikenal: ${token}` };
|
|
1081
|
-
}
|
|
1314
|
+
tools.push(TOOL_DEFINITIONS[index].key);
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1082
1317
|
|
|
1083
|
-
|
|
1318
|
+
const key = TOOL_LOOKUP.get(token.toLowerCase());
|
|
1319
|
+
if (!key) {
|
|
1320
|
+
return { error: `Nama tool tidak dikenal: ${token}` };
|
|
1084
1321
|
}
|
|
1085
1322
|
|
|
1086
|
-
|
|
1323
|
+
tools.push(key);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
return { tools: unique(tools) };
|
|
1087
1327
|
}
|
|
1088
1328
|
|
|
1089
1329
|
async function promptForMetadata(seed) {
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1330
|
+
const prompt = createPrompt();
|
|
1331
|
+
|
|
1332
|
+
try {
|
|
1333
|
+
const name =
|
|
1334
|
+
seed.name !== undefined
|
|
1335
|
+
? seed.name
|
|
1336
|
+
: await askQuestion(
|
|
1337
|
+
prompt,
|
|
1338
|
+
"Kamu mau dipanggil apa? (Kosong = Skip): ",
|
|
1339
|
+
);
|
|
1340
|
+
const project =
|
|
1341
|
+
seed.project !== undefined
|
|
1342
|
+
? seed.project
|
|
1343
|
+
: await askQuestion(prompt, "Nama project ini apa? (Kosong = Skip): ");
|
|
1344
|
+
const communication =
|
|
1345
|
+
seed.communicationLanguage !== undefined
|
|
1346
|
+
? seed.communicationLanguage
|
|
1347
|
+
: await askQuestion(
|
|
1348
|
+
prompt,
|
|
1349
|
+
"Bahasa komunikasi yang anda inginkan? (Kosong = Bahasa Indonesia): ",
|
|
1350
|
+
);
|
|
1351
|
+
const documents =
|
|
1352
|
+
seed.documentLanguage !== undefined
|
|
1353
|
+
? seed.documentLanguage
|
|
1354
|
+
: await askQuestion(
|
|
1355
|
+
prompt,
|
|
1356
|
+
"Bahasa dokumen yang dihasilkan? (Kosong = Bahasa Indonesia): ",
|
|
1357
|
+
);
|
|
1112
1358
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
assertManagedFilesUnchanged(agentsDirectory);
|
|
1123
|
-
const previousManagedSkills = validateManagedSkillNames(
|
|
1124
|
-
readNonEmptyLines(path.join(agentsDirectory, "macca-managed-skills.txt")),
|
|
1125
|
-
".agents/macca-managed-skills.txt"
|
|
1126
|
-
);
|
|
1127
|
-
const entries = [];
|
|
1128
|
-
for (const destination of getUniqueDestinations(targetDir, tools)) {
|
|
1129
|
-
for (const skillName of managedSkills) {
|
|
1130
|
-
entries.push({
|
|
1131
|
-
sourcePath: path.join(SOURCE_SKILLS_DIR, skillName),
|
|
1132
|
-
targetPath: resolveOwnedSkillPath(destination, skillName),
|
|
1133
|
-
legacySkillName: skillName
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
const configPath = path.join(agentsDirectory, "developer-config.json");
|
|
1138
|
-
const config = mergeObjects(readDeveloperConfig(configPath), buildDeveloperConfig(metadata));
|
|
1139
|
-
const managedContent = fs.existsSync(SOURCE_MANAGED_SKILLS_FILE)
|
|
1140
|
-
? fs.readFileSync(SOURCE_MANAGED_SKILLS_FILE, "utf8")
|
|
1141
|
-
: `${managedSkills.join("\n")}\n`;
|
|
1142
|
-
const lockContent = buildInstalledLock(managedSkills);
|
|
1143
|
-
const toolsContent = `${tools.join("\n")}\n`;
|
|
1144
|
-
const stateContent = buildStateContent({
|
|
1145
|
-
"macca-managed-skills.txt": managedContent,
|
|
1146
|
-
"macca-lock.json": lockContent,
|
|
1147
|
-
"macca-tools.txt": toolsContent
|
|
1148
|
-
});
|
|
1149
|
-
entries.push(
|
|
1150
|
-
{
|
|
1151
|
-
kind: "file",
|
|
1152
|
-
content: managedContent,
|
|
1153
|
-
targetPath: path.join(agentsDirectory, "macca-managed-skills.txt")
|
|
1154
|
-
},
|
|
1155
|
-
{ kind: "file", content: lockContent, targetPath: path.join(agentsDirectory, "macca-lock.json") },
|
|
1156
|
-
{ kind: "file", content: toolsContent, targetPath: path.join(agentsDirectory, "macca-tools.txt") },
|
|
1157
|
-
{ kind: "file", content: `${JSON.stringify(config, null, 2)}\n`, targetPath: configPath },
|
|
1158
|
-
{ kind: "file", content: stateContent, targetPath: path.join(agentsDirectory, STATE_FILE) }
|
|
1159
|
-
);
|
|
1160
|
-
applySkillTransaction(entries, targetDir);
|
|
1359
|
+
return {
|
|
1360
|
+
name: name || "",
|
|
1361
|
+
project: project || "",
|
|
1362
|
+
communicationLanguage: communication || "Bahasa Indonesia",
|
|
1363
|
+
documentLanguage: documents || "Bahasa Indonesia",
|
|
1364
|
+
};
|
|
1365
|
+
} finally {
|
|
1366
|
+
prompt.close();
|
|
1367
|
+
}
|
|
1161
1368
|
}
|
|
1162
1369
|
|
|
1163
|
-
function
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1370
|
+
function applyInstall(targetDir, tools, metadata, options = {}) {
|
|
1371
|
+
const force = Boolean(options.force);
|
|
1372
|
+
const managedSkills = getSourceManagedSkills();
|
|
1373
|
+
const agentsDirectory = path.join(targetDir, ".agents");
|
|
1374
|
+
ensureDirectory(targetDir);
|
|
1375
|
+
assertSafeProjectPath(targetDir, agentsDirectory);
|
|
1376
|
+
assertPackageNotOlderThanInstalled(agentsDirectory);
|
|
1377
|
+
for (const fileName of [
|
|
1378
|
+
"macca-managed-skills.txt",
|
|
1379
|
+
"macca-tools.txt",
|
|
1380
|
+
"developer-config.json",
|
|
1381
|
+
"macca-lock.json",
|
|
1382
|
+
STATE_FILE,
|
|
1383
|
+
]) {
|
|
1384
|
+
assertSafeProjectPath(targetDir, path.join(agentsDirectory, fileName));
|
|
1385
|
+
}
|
|
1386
|
+
assertManagedFilesUnchanged(agentsDirectory, force);
|
|
1387
|
+
const previousManagedSkills = validateManagedSkillNames(
|
|
1388
|
+
readNonEmptyLines(path.join(agentsDirectory, "macca-managed-skills.txt")),
|
|
1389
|
+
".agents/macca-managed-skills.txt",
|
|
1390
|
+
);
|
|
1391
|
+
const entries = [];
|
|
1392
|
+
for (const destination of getUniqueDestinations(targetDir, tools)) {
|
|
1393
|
+
for (const skillName of managedSkills) {
|
|
1394
|
+
entries.push({
|
|
1395
|
+
sourcePath: path.join(SOURCE_SKILLS_DIR, skillName),
|
|
1396
|
+
targetPath: resolveOwnedSkillPath(destination, skillName),
|
|
1397
|
+
legacySkillName: skillName,
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
const configPath = path.join(agentsDirectory, "developer-config.json");
|
|
1402
|
+
const config = mergeObjects(
|
|
1403
|
+
readDeveloperConfig(configPath),
|
|
1404
|
+
buildDeveloperConfig(metadata),
|
|
1405
|
+
);
|
|
1406
|
+
const managedContent = fs.existsSync(SOURCE_MANAGED_SKILLS_FILE)
|
|
1407
|
+
? fs.readFileSync(SOURCE_MANAGED_SKILLS_FILE, "utf8")
|
|
1408
|
+
: `${managedSkills.join("\n")}\n`;
|
|
1409
|
+
const lockContent = buildInstalledLock(managedSkills);
|
|
1410
|
+
const toolsContent = `${tools.join("\n")}\n`;
|
|
1411
|
+
const stateContent = buildStateContent({
|
|
1412
|
+
"macca-managed-skills.txt": managedContent,
|
|
1413
|
+
"macca-lock.json": lockContent,
|
|
1414
|
+
"macca-tools.txt": toolsContent,
|
|
1415
|
+
});
|
|
1416
|
+
entries.push(
|
|
1417
|
+
{
|
|
1418
|
+
kind: "file",
|
|
1419
|
+
content: managedContent,
|
|
1420
|
+
targetPath: path.join(agentsDirectory, "macca-managed-skills.txt"),
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
kind: "file",
|
|
1424
|
+
content: lockContent,
|
|
1425
|
+
targetPath: path.join(agentsDirectory, "macca-lock.json"),
|
|
1426
|
+
},
|
|
1427
|
+
{
|
|
1428
|
+
kind: "file",
|
|
1429
|
+
content: toolsContent,
|
|
1430
|
+
targetPath: path.join(agentsDirectory, "macca-tools.txt"),
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
kind: "file",
|
|
1434
|
+
content: `${JSON.stringify(config, null, 2)}\n`,
|
|
1435
|
+
targetPath: configPath,
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
kind: "file",
|
|
1439
|
+
content: stateContent,
|
|
1440
|
+
targetPath: path.join(agentsDirectory, STATE_FILE),
|
|
1441
|
+
},
|
|
1442
|
+
);
|
|
1443
|
+
applySkillTransaction(entries, targetDir, force);
|
|
1444
|
+
}
|
|
1203
1445
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
"macca-managed-skills.txt": managedContent,
|
|
1226
|
-
"macca-lock.json": lockContent,
|
|
1227
|
-
"macca-tools.txt": toolsContent
|
|
1228
|
-
});
|
|
1229
|
-
entries.push(
|
|
1230
|
-
{
|
|
1231
|
-
kind: "file",
|
|
1232
|
-
content: managedContent,
|
|
1233
|
-
targetPath: path.join(agentsDirectory, "macca-managed-skills.txt")
|
|
1234
|
-
},
|
|
1235
|
-
{ kind: "file", content: lockContent, targetPath: path.join(agentsDirectory, "macca-lock.json") },
|
|
1236
|
-
{ kind: "file", content: stateContent, targetPath: path.join(agentsDirectory, STATE_FILE) }
|
|
1446
|
+
function applyUpgrade(targetDir, options = {}) {
|
|
1447
|
+
const force = Boolean(options.force);
|
|
1448
|
+
const agentsDirectory = path.join(targetDir, ".agents");
|
|
1449
|
+
assertSafeProjectPath(targetDir, agentsDirectory);
|
|
1450
|
+
assertPackageNotOlderThanInstalled(agentsDirectory);
|
|
1451
|
+
for (const fileName of [
|
|
1452
|
+
"macca-managed-skills.txt",
|
|
1453
|
+
"macca-tools.txt",
|
|
1454
|
+
"developer-config.json",
|
|
1455
|
+
"macca-lock.json",
|
|
1456
|
+
STATE_FILE,
|
|
1457
|
+
]) {
|
|
1458
|
+
assertSafeProjectPath(targetDir, path.join(agentsDirectory, fileName));
|
|
1459
|
+
}
|
|
1460
|
+
assertManagedFilesUnchanged(agentsDirectory, force);
|
|
1461
|
+
const tools = readNonEmptyLines(
|
|
1462
|
+
path.join(agentsDirectory, "macca-tools.txt"),
|
|
1463
|
+
);
|
|
1464
|
+
if (tools.length === 0) {
|
|
1465
|
+
throw new Error(
|
|
1466
|
+
".agents/macca-tools.txt was not found. Run install first so MACCA knows which tools to update.",
|
|
1237
1467
|
);
|
|
1238
|
-
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
const previousManagedSkills = validateManagedSkillNames(
|
|
1471
|
+
readNonEmptyLines(path.join(agentsDirectory, "macca-managed-skills.txt")),
|
|
1472
|
+
".agents/macca-managed-skills.txt",
|
|
1473
|
+
);
|
|
1474
|
+
const nextManagedSkills = getSourceManagedSkills();
|
|
1475
|
+
const entries = [];
|
|
1476
|
+
for (const destination of getUniqueDestinations(targetDir, tools)) {
|
|
1477
|
+
for (const skillName of nextManagedSkills) {
|
|
1478
|
+
entries.push({
|
|
1479
|
+
sourcePath: path.join(SOURCE_SKILLS_DIR, skillName),
|
|
1480
|
+
targetPath: resolveOwnedSkillPath(destination, skillName),
|
|
1481
|
+
legacySkillName: skillName,
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
for (const skillName of previousManagedSkills.filter(
|
|
1486
|
+
(name) => !nextManagedSkills.includes(name),
|
|
1487
|
+
)) {
|
|
1488
|
+
const oldPath = resolveOwnedSkillPath(destination, skillName);
|
|
1489
|
+
if (fs.existsSync(oldPath) && isMaccaOwned(oldPath)) {
|
|
1490
|
+
assertManagedDirectoryUnchanged(oldPath, force);
|
|
1491
|
+
entries.push({ sourcePath: null, targetPath: oldPath });
|
|
1492
|
+
} else if (isLegacyDirectoryUnchanged(oldPath, skillName)) {
|
|
1493
|
+
entries.push({
|
|
1494
|
+
sourcePath: null,
|
|
1495
|
+
targetPath: oldPath,
|
|
1496
|
+
legacySkillName: skillName,
|
|
1497
|
+
});
|
|
1498
|
+
} else if (fs.existsSync(oldPath)) {
|
|
1499
|
+
process.stderr.write(
|
|
1500
|
+
`Preserving unmarked legacy skill directory: ${oldPath}\n`,
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
if (tools.includes("opencode")) {
|
|
1507
|
+
const legacyOpenCodeRoot = path.join(targetDir, ".opencode", "skill");
|
|
1508
|
+
assertSafeProjectPath(targetDir, legacyOpenCodeRoot);
|
|
1509
|
+
for (const skillName of previousManagedSkills) {
|
|
1510
|
+
const oldPath = resolveOwnedSkillPath(legacyOpenCodeRoot, skillName);
|
|
1511
|
+
if (fs.existsSync(oldPath) && isMaccaOwned(oldPath)) {
|
|
1512
|
+
assertManagedDirectoryUnchanged(oldPath, force);
|
|
1513
|
+
entries.push({ sourcePath: null, targetPath: oldPath });
|
|
1514
|
+
} else if (isLegacyDirectoryUnchanged(oldPath, skillName)) {
|
|
1515
|
+
entries.push({
|
|
1516
|
+
sourcePath: null,
|
|
1517
|
+
targetPath: oldPath,
|
|
1518
|
+
legacySkillName: skillName,
|
|
1519
|
+
});
|
|
1520
|
+
} else if (fs.existsSync(oldPath)) {
|
|
1521
|
+
if (!force) {
|
|
1522
|
+
throw new Error(
|
|
1523
|
+
`Legacy OpenCode skill was modified; move or back it up before upgrade: ${oldPath}. ` +
|
|
1524
|
+
"Run upgrade with --force to overwrite local modifications.",
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
const managedContent = fs.existsSync(SOURCE_MANAGED_SKILLS_FILE)
|
|
1531
|
+
? fs.readFileSync(SOURCE_MANAGED_SKILLS_FILE, "utf8")
|
|
1532
|
+
: `${nextManagedSkills.join("\n")}\n`;
|
|
1533
|
+
const lockContent = buildInstalledLock(nextManagedSkills);
|
|
1534
|
+
const toolsContent = `${tools.join("\n")}\n`;
|
|
1535
|
+
const stateContent = buildStateContent({
|
|
1536
|
+
"macca-managed-skills.txt": managedContent,
|
|
1537
|
+
"macca-lock.json": lockContent,
|
|
1538
|
+
"macca-tools.txt": toolsContent,
|
|
1539
|
+
});
|
|
1540
|
+
entries.push(
|
|
1541
|
+
{
|
|
1542
|
+
kind: "file",
|
|
1543
|
+
content: managedContent,
|
|
1544
|
+
targetPath: path.join(agentsDirectory, "macca-managed-skills.txt"),
|
|
1545
|
+
},
|
|
1546
|
+
{
|
|
1547
|
+
kind: "file",
|
|
1548
|
+
content: lockContent,
|
|
1549
|
+
targetPath: path.join(agentsDirectory, "macca-lock.json"),
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
kind: "file",
|
|
1553
|
+
content: stateContent,
|
|
1554
|
+
targetPath: path.join(agentsDirectory, STATE_FILE),
|
|
1555
|
+
},
|
|
1556
|
+
);
|
|
1557
|
+
applySkillTransaction(entries, targetDir, force);
|
|
1239
1558
|
|
|
1240
|
-
|
|
1559
|
+
if (tools.includes("kimi")) reportLegacyKimiInstall();
|
|
1241
1560
|
}
|
|
1242
1561
|
|
|
1243
1562
|
function getLegacyKimiRoot() {
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1563
|
+
if (process.platform === "win32") {
|
|
1564
|
+
return path.join(
|
|
1565
|
+
process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"),
|
|
1566
|
+
"agents",
|
|
1567
|
+
"skills",
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
return path.join(os.homedir(), ".config", "agents", "skills");
|
|
1252
1571
|
}
|
|
1253
1572
|
|
|
1254
1573
|
function reportLegacyKimiInstall() {
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1574
|
+
const legacyRoot = getLegacyKimiRoot();
|
|
1575
|
+
if (!fs.existsSync(legacyRoot)) return;
|
|
1576
|
+
|
|
1577
|
+
const legacyHashes = getLegacyPayloadHashes();
|
|
1578
|
+
const detected = [];
|
|
1579
|
+
for (const [skillName, acceptedHashes] of Object.entries(legacyHashes)) {
|
|
1580
|
+
const skillPath = path.join(legacyRoot, skillName);
|
|
1581
|
+
if (!fs.existsSync(skillPath)) continue;
|
|
1582
|
+
const stat = fs.lstatSync(skillPath);
|
|
1583
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) continue;
|
|
1584
|
+
const payloadHash = hashDirectory(skillPath);
|
|
1585
|
+
detected.push({ skillPath, unchanged: acceptedHashes.has(payloadHash) });
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
if (!detected.length) return;
|
|
1589
|
+
process.stderr.write(
|
|
1590
|
+
`Legacy Kimi copies remain outside this project at ${legacyRoot}. ` +
|
|
1591
|
+
"MACCA did not delete global files. Verify the new project-local .agents/skills installation, " +
|
|
1592
|
+
"then remove old copies manually if your Kimi version still discovers them.\n",
|
|
1593
|
+
);
|
|
1275
1594
|
}
|
|
1276
1595
|
|
|
1277
1596
|
function printInstallSummary(action, tools) {
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1597
|
+
process.stdout.write(`\n MACCA ${action} untuk:\n`);
|
|
1598
|
+
for (const toolKey of tools) {
|
|
1599
|
+
const tool = TOOL_BY_KEY.get(toolKey);
|
|
1600
|
+
process.stdout.write(
|
|
1601
|
+
` ✓ ${tool.label.padEnd(16)} -> ${tool.displayDestination}\n`,
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
process.stdout.write("\n");
|
|
1284
1605
|
}
|
|
1285
1606
|
|
|
1286
1607
|
async function runInstall(args) {
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1608
|
+
let tools = normalizeToolList(args.tools);
|
|
1609
|
+
if (tools.length === 0) {
|
|
1610
|
+
tools = args.yes ? ["codex"] : await promptForTools();
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
const targetDir = assertSafeTargetDirectory(args.directory);
|
|
1614
|
+
ensureDirectory(targetDir);
|
|
1615
|
+
assertSafeProjectPath(targetDir, path.join(targetDir, ".agents"));
|
|
1616
|
+
recoverInterruptedTransaction(targetDir);
|
|
1617
|
+
const existingTools = readNonEmptyLines(
|
|
1618
|
+
path.join(targetDir, ".agents", "macca-tools.txt"),
|
|
1619
|
+
);
|
|
1620
|
+
tools = unique([...existingTools, ...tools]);
|
|
1621
|
+
const configPath = path.join(targetDir, ".agents", "developer-config.json");
|
|
1622
|
+
const existingConfig = readDeveloperConfig(configPath);
|
|
1623
|
+
const hasExistingConfig = fs.existsSync(configPath);
|
|
1624
|
+
const metadata = args.yes
|
|
1625
|
+
? {
|
|
1626
|
+
name:
|
|
1627
|
+
args.name !== undefined
|
|
1628
|
+
? args.name
|
|
1629
|
+
: hasExistingConfig
|
|
1630
|
+
? undefined
|
|
1631
|
+
: "",
|
|
1632
|
+
project:
|
|
1633
|
+
args.project !== undefined
|
|
1634
|
+
? args.project
|
|
1635
|
+
: hasExistingConfig
|
|
1636
|
+
? undefined
|
|
1637
|
+
: "",
|
|
1638
|
+
communicationLanguage:
|
|
1639
|
+
args.communicationLanguage !== undefined
|
|
1640
|
+
? args.communicationLanguage
|
|
1641
|
+
: hasExistingConfig
|
|
1642
|
+
? undefined
|
|
1643
|
+
: "Bahasa Indonesia",
|
|
1644
|
+
documentLanguage:
|
|
1645
|
+
args.documentLanguage !== undefined
|
|
1646
|
+
? args.documentLanguage
|
|
1647
|
+
: hasExistingConfig
|
|
1648
|
+
? undefined
|
|
1649
|
+
: "Bahasa Indonesia",
|
|
1650
|
+
}
|
|
1651
|
+
: await promptForMetadata({
|
|
1652
|
+
...args,
|
|
1653
|
+
name: args.name !== undefined ? args.name : existingConfig.name,
|
|
1654
|
+
project:
|
|
1655
|
+
args.project !== undefined ? args.project : existingConfig.project,
|
|
1656
|
+
communicationLanguage:
|
|
1657
|
+
args.communicationLanguage !== undefined
|
|
1658
|
+
? args.communicationLanguage
|
|
1659
|
+
: existingConfig.languagePreferences?.communication?.raw,
|
|
1660
|
+
documentLanguage:
|
|
1661
|
+
args.documentLanguage !== undefined
|
|
1662
|
+
? args.documentLanguage
|
|
1663
|
+
: existingConfig.languagePreferences?.documents?.raw,
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
applyInstall(targetDir, tools, metadata, { force: Boolean(args.force) });
|
|
1667
|
+
|
|
1668
|
+
printInstallSummary("installed", tools);
|
|
1669
|
+
process.stdout.write(` Target project: ${targetDir}\n\n`);
|
|
1328
1670
|
}
|
|
1329
1671
|
|
|
1330
1672
|
function runUpgrade(args) {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1673
|
+
const targetDir = assertSafeTargetDirectory(args.directory);
|
|
1674
|
+
assertSafeProjectPath(targetDir, path.join(targetDir, ".agents"));
|
|
1675
|
+
recoverInterruptedTransaction(targetDir);
|
|
1676
|
+
const tools = readNonEmptyLines(
|
|
1677
|
+
path.join(targetDir, ".agents", "macca-tools.txt"),
|
|
1678
|
+
);
|
|
1679
|
+
applyUpgrade(targetDir, { force: Boolean(args.force) });
|
|
1680
|
+
printInstallSummary("updated", tools);
|
|
1681
|
+
process.stdout.write(` Target project: ${targetDir}\n\n`);
|
|
1338
1682
|
}
|
|
1339
1683
|
|
|
1340
1684
|
function exitWithError(message) {
|
|
1341
|
-
|
|
1342
|
-
|
|
1685
|
+
process.stderr.write(`\nError: ${message}\n`);
|
|
1686
|
+
process.exit(1);
|
|
1343
1687
|
}
|
|
1344
1688
|
|
|
1345
1689
|
main();
|