@xn-intenton-z2a/agentic-lib 7.1.6

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.
Files changed (53) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +323 -0
  3. package/bin/agentic-lib.js +765 -0
  4. package/package.json +102 -0
  5. package/src/actions/agentic-step/action.yml +58 -0
  6. package/src/actions/agentic-step/config-loader.js +153 -0
  7. package/src/actions/agentic-step/copilot.js +170 -0
  8. package/src/actions/agentic-step/index.js +118 -0
  9. package/src/actions/agentic-step/logging.js +88 -0
  10. package/src/actions/agentic-step/package-lock.json +1891 -0
  11. package/src/actions/agentic-step/package.json +29 -0
  12. package/src/actions/agentic-step/safety.js +103 -0
  13. package/src/actions/agentic-step/tasks/discussions.js +141 -0
  14. package/src/actions/agentic-step/tasks/enhance-issue.js +102 -0
  15. package/src/actions/agentic-step/tasks/fix-code.js +71 -0
  16. package/src/actions/agentic-step/tasks/maintain-features.js +79 -0
  17. package/src/actions/agentic-step/tasks/maintain-library.js +67 -0
  18. package/src/actions/agentic-step/tasks/resolve-issue.js +98 -0
  19. package/src/actions/agentic-step/tasks/review-issue.js +121 -0
  20. package/src/actions/agentic-step/tasks/transform.js +213 -0
  21. package/src/actions/agentic-step/tools.js +142 -0
  22. package/src/actions/commit-if-changed/action.yml +39 -0
  23. package/src/actions/setup-npmrc/action.yml +38 -0
  24. package/src/agents/agent-apply-fix.md +13 -0
  25. package/src/agents/agent-discussion-bot.md +35 -0
  26. package/src/agents/agent-issue-resolution.md +13 -0
  27. package/src/agents/agent-maintain-features.md +29 -0
  28. package/src/agents/agent-maintain-library.md +31 -0
  29. package/src/agents/agent-ready-issue.md +13 -0
  30. package/src/agents/agent-review-issue.md +2 -0
  31. package/src/agents/agentic-lib.yml +68 -0
  32. package/src/scripts/accept-release.sh +29 -0
  33. package/src/scripts/activate-schedule.sh +41 -0
  34. package/src/scripts/clean.sh +21 -0
  35. package/src/scripts/generate-library-index.js +143 -0
  36. package/src/scripts/initialise.sh +39 -0
  37. package/src/scripts/md-to-html.js +77 -0
  38. package/src/scripts/update.sh +19 -0
  39. package/src/seeds/test.yml +33 -0
  40. package/src/seeds/zero-MISSION.md +7 -0
  41. package/src/seeds/zero-README.md +14 -0
  42. package/src/seeds/zero-agentic-lib.toml +32 -0
  43. package/src/seeds/zero-main.js +15 -0
  44. package/src/seeds/zero-main.test.js +11 -0
  45. package/src/seeds/zero-package.json +26 -0
  46. package/src/workflows/agent-discussions-bot.yml +78 -0
  47. package/src/workflows/agent-flow-fix-code.yml +98 -0
  48. package/src/workflows/agent-flow-maintain.yml +114 -0
  49. package/src/workflows/agent-flow-review.yml +99 -0
  50. package/src/workflows/agent-flow-transform.yml +82 -0
  51. package/src/workflows/agent-supervisor.yml +85 -0
  52. package/src/workflows/ci-automerge.yml +544 -0
  53. package/src/workflows/ci-init.yml +63 -0
@@ -0,0 +1,765 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: GPL-3.0-only
3
+ // Copyright (C) 2025-2026 Polycode Limited
4
+ // bin/agentic-lib.js — CLI for @xn-intenton-z2a/agentic-lib
5
+ //
6
+ // Infrastructure commands:
7
+ // npx @xn-intenton-z2a/agentic-lib init # set up agentic infrastructure
8
+ // npx @xn-intenton-z2a/agentic-lib init --purge # also reset source files to seeds
9
+ // npx @xn-intenton-z2a/agentic-lib reset # alias for init --purge
10
+ //
11
+ // Task commands (run Copilot SDK transformations locally):
12
+ // npx @xn-intenton-z2a/agentic-lib transform
13
+ // npx @xn-intenton-z2a/agentic-lib maintain-features
14
+ // npx @xn-intenton-z2a/agentic-lib maintain-library
15
+ // npx @xn-intenton-z2a/agentic-lib fix-code
16
+
17
+ import { copyFileSync, existsSync, mkdirSync, rmSync, readdirSync, readFileSync, writeFileSync } from "fs";
18
+ import { resolve, dirname, join } from "path";
19
+ import { fileURLToPath } from "url";
20
+ import { execSync } from "child_process";
21
+
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ const pkgRoot = resolve(__dirname, "..");
24
+ const srcDir = resolve(pkgRoot, "src");
25
+
26
+ const args = process.argv.slice(2);
27
+ const command = args[0];
28
+ const flags = args.slice(1);
29
+
30
+ let initChanges = 0;
31
+ const TASK_COMMANDS = ["transform", "maintain-features", "maintain-library", "fix-code"];
32
+ const INIT_COMMANDS = ["init", "update", "reset"];
33
+ const ALL_COMMANDS = [...INIT_COMMANDS, ...TASK_COMMANDS, "version"];
34
+
35
+ const HELP = `
36
+ @xn-intenton-z2a/agentic-lib — Agentic Coding Systems SDK
37
+
38
+ Infrastructure:
39
+ init [--purge] Set up or update agentic infrastructure
40
+ update Alias for init
41
+ reset Alias for init --purge
42
+ version Show version
43
+
44
+ Tasks (run Copilot SDK transformations):
45
+ transform Transform code toward the mission
46
+ maintain-features Generate feature files from mission
47
+ maintain-library Update library docs from SOURCES.md
48
+ fix-code Fix failing tests
49
+
50
+ Options:
51
+ --purge Also reset source files to seed state
52
+ --dry-run Show what would be done without making changes
53
+ --target <path> Target repository (default: current directory)
54
+ --model <name> Copilot SDK model (default: claude-sonnet-4)
55
+
56
+ Examples:
57
+ npx @xn-intenton-z2a/agentic-lib init
58
+ npx @xn-intenton-z2a/agentic-lib transform
59
+ npx @xn-intenton-z2a/agentic-lib maintain-features --model gpt-5-mini
60
+ npx @xn-intenton-z2a/agentic-lib reset --dry-run
61
+ `.trim();
62
+
63
+ if (!command || command === "--help" || command === "-h" || command === "help") {
64
+ console.log(HELP);
65
+ process.exit(0);
66
+ }
67
+
68
+ if (command === "version" || command === "--version" || command === "-v") {
69
+ const pkg = JSON.parse(readFileSync(resolve(pkgRoot, "package.json"), "utf8"));
70
+ console.log(pkg.version);
71
+ process.exit(0);
72
+ }
73
+
74
+ // Parse flags
75
+ const dryRun = flags.includes("--dry-run");
76
+ const targetIdx = flags.indexOf("--target");
77
+ const targetPath = targetIdx >= 0 ? flags[targetIdx + 1] : process.cwd();
78
+ const target = resolve(targetPath);
79
+ const modelIdx = flags.indexOf("--model");
80
+ const model = modelIdx >= 0 ? flags[modelIdx + 1] : "claude-sonnet-4";
81
+
82
+ // ─── Task Commands ───────────────────────────────────────────────────
83
+
84
+ if (TASK_COMMANDS.includes(command)) {
85
+ process.exit(await runTask(command));
86
+ }
87
+
88
+ // ─── Init Commands ───────────────────────────────────────────────────
89
+
90
+ let purge = flags.includes("--purge");
91
+ if (command === "reset") purge = true;
92
+
93
+ if (!ALL_COMMANDS.includes(command)) {
94
+ console.error(`Unknown command: ${command}`);
95
+ console.error("Run with --help for usage.");
96
+ process.exit(1);
97
+ }
98
+
99
+ runInit();
100
+
101
+ // ─── Task Runner ─────────────────────────────────────────────────────
102
+
103
+ async function runTask(taskName) {
104
+ console.log("");
105
+ console.log(`=== agentic-lib ${taskName} ===`);
106
+ console.log(`Target: ${target}`);
107
+ console.log(`Model: ${model}`);
108
+ console.log(`Dry-run: ${dryRun}`);
109
+ console.log("");
110
+
111
+ // Find the Copilot SDK
112
+ const sdkLocations = [
113
+ resolve(pkgRoot, "node_modules/@github/copilot-sdk/dist/index.js"),
114
+ resolve(pkgRoot, "src/actions/agentic-step/node_modules/@github/copilot-sdk/dist/index.js"),
115
+ resolve(target, ".github/agentic-lib/actions/agentic-step/node_modules/@github/copilot-sdk/dist/index.js"),
116
+ ];
117
+ const sdkPath = sdkLocations.find((p) => existsSync(p));
118
+ if (!sdkPath) {
119
+ console.error("ERROR: @github/copilot-sdk not found.");
120
+ console.error("Run: cd .github/agentic-lib/actions/agentic-step && npm ci");
121
+ return 1;
122
+ }
123
+ const { CopilotClient, approveAll, defineTool } = await import(sdkPath);
124
+
125
+ // Load config
126
+ const config = await loadTaskConfig();
127
+ const writablePaths = getWritablePathsFromConfig(config);
128
+ const readOnlyPaths = getReadOnlyPathsFromConfig(config);
129
+
130
+ console.log(`[config] schedule=${config.schedule}`);
131
+ console.log(`[config] writable=${writablePaths.join(", ")}`);
132
+ console.log(`[config] test=${config.testScript}`);
133
+ console.log("");
134
+
135
+ // Build task-specific prompt
136
+ const { systemMessage, prompt } = buildTaskPrompt(taskName, config, writablePaths, readOnlyPaths);
137
+
138
+ if (!prompt) {
139
+ return 0; // buildTaskPrompt already logged why
140
+ }
141
+
142
+ console.log(`[prompt] ${prompt.length} chars`);
143
+ console.log("");
144
+
145
+ if (dryRun) {
146
+ console.log("=== DRY RUN — prompt constructed but not sent ===");
147
+ console.log("");
148
+ console.log(prompt);
149
+ return 0;
150
+ }
151
+
152
+ // Create tools
153
+ const tools = createCliTools(writablePaths, defineTool);
154
+
155
+ // Set up auth
156
+ const copilotToken = process.env.COPILOT_GITHUB_TOKEN;
157
+ if (!copilotToken) {
158
+ console.error("ERROR: COPILOT_GITHUB_TOKEN is required. Set it in your environment.");
159
+ return 1;
160
+ }
161
+ console.log("[auth] Using COPILOT_GITHUB_TOKEN");
162
+ const clientOptions = {};
163
+ const env = { ...process.env };
164
+ env.GITHUB_TOKEN = copilotToken;
165
+ env.GH_TOKEN = copilotToken;
166
+ clientOptions.env = env;
167
+
168
+ const client = new CopilotClient(clientOptions);
169
+
170
+ try {
171
+ console.log("[copilot] Creating session...");
172
+ const session = await client.createSession({
173
+ model,
174
+ systemMessage: { content: systemMessage },
175
+ tools,
176
+ onPermissionRequest: approveAll,
177
+ workingDirectory: target,
178
+ });
179
+ console.log(`[copilot] Session: ${session.sessionId}`);
180
+
181
+ // Verbose event logging
182
+ session.on((event) => {
183
+ const type = event?.type || "unknown";
184
+ if (type === "assistant.message") {
185
+ const preview = event?.data?.content?.substring(0, 120) || "";
186
+ console.log(`[event] ${type}: ${preview}...`);
187
+ } else if (type === "tool.call") {
188
+ const name = event?.data?.name || "?";
189
+ const args = JSON.stringify(event?.data?.arguments || {}).substring(0, 200);
190
+ console.log(`[event] tool.call: ${name}(${args})`);
191
+ } else if (type === "session.error") {
192
+ console.error(`[event] ERROR: ${JSON.stringify(event?.data || event)}`);
193
+ } else if (type !== "session.idle") {
194
+ console.log(`[event] ${type}`);
195
+ }
196
+ });
197
+
198
+ const startTime = Date.now();
199
+ console.log("[copilot] Sending prompt...");
200
+ const response = await session.sendAndWait({ prompt }, 300000);
201
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
202
+
203
+ const content = response?.data?.content || "(no content)";
204
+ const tokens = response?.data?.usage?.totalTokens || 0;
205
+
206
+ console.log("");
207
+ console.log(`=== ${taskName} completed in ${elapsed}s (${tokens} tokens) ===`);
208
+ console.log("");
209
+ console.log(content);
210
+ console.log("");
211
+
212
+ return 0;
213
+ } catch (err) {
214
+ console.error("");
215
+ console.error(`=== ${taskName} FAILED ===`);
216
+ console.error(err.message);
217
+ if (err.stack) console.error(err.stack);
218
+ return 1;
219
+ } finally {
220
+ await client.stop();
221
+ }
222
+ }
223
+
224
+ // ─── Task Config + Prompts ───────────────────────────────────────────
225
+
226
+ async function loadTaskConfig() {
227
+ const tomlPath = resolve(target, "agentic-lib.toml");
228
+
229
+ if (!existsSync(tomlPath)) {
230
+ throw new Error(`Config file not found: ${tomlPath}. Create agentic-lib.toml in the project root.`);
231
+ }
232
+
233
+ console.log(`[config] Loading ${tomlPath}`);
234
+ const { parse } = await import("smol-toml");
235
+ const toml = parse(readFileSync(tomlPath, "utf8"));
236
+ return {
237
+ schedule: toml.schedule?.tier || "schedule-1",
238
+ missionPath: toml.paths?.mission || "MISSION.md",
239
+ sourcePath: toml.paths?.source || "src/lib/",
240
+ testsPath: toml.paths?.tests || "tests/unit/",
241
+ featuresPath: toml.paths?.features || ".github/agentic-lib/features/",
242
+ libraryPath: toml.paths?.docs || "library/",
243
+ sourcesPath: toml.paths?.["library-sources"] || "SOURCES.md",
244
+ readmePath: toml.paths?.readme || "README.md",
245
+ depsPath: toml.paths?.dependencies || "package.json",
246
+ buildScript: toml.execution?.build || "npm run build",
247
+ testScript: toml.execution?.test || "npm test",
248
+ mainScript: toml.execution?.start || "npm run start",
249
+ featureLimit: toml.limits?.["feature-issues"] || 2,
250
+ intentionPath: toml.bot?.["log-file"] || "intentïon.md",
251
+ };
252
+ }
253
+
254
+ function getWritablePathsFromConfig(config) {
255
+ return [
256
+ config.sourcePath,
257
+ config.testsPath,
258
+ config.featuresPath,
259
+ config.libraryPath,
260
+ config.readmePath,
261
+ config.depsPath,
262
+ ].filter(Boolean);
263
+ }
264
+
265
+ function getReadOnlyPathsFromConfig(config) {
266
+ return [config.missionPath, config.sourcesPath].filter(Boolean);
267
+ }
268
+
269
+ function readOptional(relPath) {
270
+ try {
271
+ return readFileSync(resolve(target, relPath), "utf8");
272
+ } catch (err) {
273
+ console.debug(`[readOptional] ${relPath}: ${err.message}`);
274
+ return "";
275
+ }
276
+ }
277
+
278
+ function scanDir(relPath, extensions, opts = {}) {
279
+ const { fileLimit = 10, contentLimit } = opts;
280
+ const dir = resolve(target, relPath);
281
+ const exts = Array.isArray(extensions) ? extensions : [extensions];
282
+ if (!existsSync(dir)) return [];
283
+ try {
284
+ return readdirSync(dir, { recursive: true })
285
+ .filter((f) => exts.some((ext) => String(f).endsWith(ext)))
286
+ .slice(0, fileLimit)
287
+ .map((f) => {
288
+ try {
289
+ const content = readFileSync(resolve(dir, String(f)), "utf8");
290
+ return { name: String(f), content: contentLimit ? content.substring(0, contentLimit) : content };
291
+ } catch (err) {
292
+ console.debug(`[scanDir] ${dir}/${f}: ${err.message}`);
293
+ return { name: String(f), content: "" };
294
+ }
295
+ });
296
+ } catch (err) {
297
+ console.debug(`[scanDir] ${dir}: ${err.message}`);
298
+ return [];
299
+ }
300
+ }
301
+
302
+ function formatPaths(writable, readOnly) {
303
+ return [
304
+ "## File Paths",
305
+ "### Writable (you may modify these)",
306
+ writable.length > 0 ? writable.map((p) => `- ${p}`).join("\n") : "- (none)",
307
+ "",
308
+ "### Read-Only (for context only, do NOT modify)",
309
+ readOnly.length > 0 ? readOnly.map((p) => `- ${p}`).join("\n") : "- (none)",
310
+ ].join("\n");
311
+ }
312
+
313
+ function buildTaskPrompt(taskName, config, writablePaths, readOnlyPaths) {
314
+ const pathsSection = formatPaths(writablePaths, readOnlyPaths);
315
+
316
+ switch (taskName) {
317
+ case "transform":
318
+ return buildTransformPrompt(config, pathsSection);
319
+ case "maintain-features":
320
+ return buildMaintainFeaturesPrompt(config, pathsSection);
321
+ case "maintain-library":
322
+ return buildMaintainLibraryPrompt(config, pathsSection);
323
+ case "fix-code":
324
+ return buildFixCodePrompt(config, pathsSection);
325
+ default:
326
+ console.error(`Unknown task: ${taskName}`);
327
+ return { systemMessage: "", prompt: null };
328
+ }
329
+ }
330
+
331
+ function buildTransformPrompt(config, pathsSection) {
332
+ const mission = readOptional(config.missionPath);
333
+ if (!mission) {
334
+ console.error(`No mission file found at ${config.missionPath}`);
335
+ return { systemMessage: "", prompt: null };
336
+ }
337
+ console.log(`[context] Mission: ${mission.substring(0, 80).trim()}...`);
338
+
339
+ const features = scanDir(config.featuresPath, ".md");
340
+ const sourceFiles = scanDir(config.sourcePath, [".js", ".ts"], { contentLimit: 2000 });
341
+ console.log(`[context] Features: ${features.length}, Source files: ${sourceFiles.length}`);
342
+
343
+ return {
344
+ systemMessage:
345
+ "You are an autonomous code transformation agent. Your goal is to advance the repository toward its mission by making the most impactful change possible in a single step.",
346
+ prompt: [
347
+ "## Instructions",
348
+ "Transform the repository toward its mission by identifying the next best action.",
349
+ "",
350
+ "## Mission",
351
+ mission,
352
+ "",
353
+ `## Current Features (${features.length})`,
354
+ ...features.map((f) => `### ${f.name}\n${f.content.substring(0, 500)}`),
355
+ "",
356
+ `## Current Source Files (${sourceFiles.length})`,
357
+ ...sourceFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
358
+ "",
359
+ "## Your Task",
360
+ "Analyze the mission, features, and source code.",
361
+ "Determine the single most impactful next step.",
362
+ "Then implement that step by writing files.",
363
+ "",
364
+ pathsSection,
365
+ "",
366
+ "## Constraints",
367
+ `- Run \`${config.testScript}\` to validate your changes`,
368
+ ].join("\n"),
369
+ };
370
+ }
371
+
372
+ function buildMaintainFeaturesPrompt(config, pathsSection) {
373
+ const mission = readOptional(config.missionPath);
374
+ if (!mission) {
375
+ console.error(`No mission file found at ${config.missionPath}`);
376
+ return { systemMessage: "", prompt: null };
377
+ }
378
+
379
+ const features = scanDir(config.featuresPath, ".md");
380
+ const libraryDocs = scanDir(config.libraryPath, ".md", { contentLimit: 1000 });
381
+ console.log(`[context] Mission loaded, features: ${features.length}, library: ${libraryDocs.length}`);
382
+
383
+ return {
384
+ systemMessage:
385
+ "You are a feature lifecycle manager. Create, update, and prune feature specification files to keep the project focused on its mission.",
386
+ prompt: [
387
+ "## Instructions",
388
+ "Maintain the feature set by creating, updating, or pruning features.",
389
+ "",
390
+ "## Mission",
391
+ mission,
392
+ "",
393
+ `## Current Features (${features.length}/${config.featureLimit} max)`,
394
+ ...features.map((f) => `### ${f.name}\n${f.content}`),
395
+ "",
396
+ libraryDocs.length > 0 ? `## Library Documents (${libraryDocs.length})` : "",
397
+ ...libraryDocs.map((d) => `### ${d.name}\n${d.content}`),
398
+ "",
399
+ "## Your Task",
400
+ `1. Review each existing feature — if it is already implemented or irrelevant, delete it.`,
401
+ `2. If there are fewer than ${config.featureLimit} features, create new features aligned with the mission.`,
402
+ "3. Ensure each feature has clear, testable acceptance criteria.",
403
+ "",
404
+ pathsSection,
405
+ "",
406
+ "## Constraints",
407
+ `- Maximum ${config.featureLimit} feature files`,
408
+ "- Feature files must be markdown with a descriptive filename",
409
+ ].join("\n"),
410
+ };
411
+ }
412
+
413
+ function buildMaintainLibraryPrompt(config, pathsSection) {
414
+ const sources = readOptional(config.sourcesPath);
415
+ if (!sources.trim()) {
416
+ console.log("No SOURCES.md or empty — nothing to maintain.");
417
+ return { systemMessage: "", prompt: null };
418
+ }
419
+
420
+ const libraryDocs = scanDir(config.libraryPath, ".md", { contentLimit: 500 });
421
+ console.log(`[context] Sources loaded, library: ${libraryDocs.length}`);
422
+
423
+ return {
424
+ systemMessage:
425
+ "You are a knowledge librarian. Maintain a library of technical documents extracted from web sources.",
426
+ prompt: [
427
+ "## Instructions",
428
+ "Maintain the library by updating documents from sources.",
429
+ "",
430
+ "## Sources",
431
+ sources,
432
+ "",
433
+ `## Current Library Documents (${libraryDocs.length})`,
434
+ ...libraryDocs.map((d) => `### ${d.name}\n${d.content}`),
435
+ "",
436
+ "## Your Task",
437
+ "1. Read each URL in SOURCES.md and extract technical content.",
438
+ "2. Create or update library documents based on the source content.",
439
+ "3. Remove library documents that no longer have corresponding sources.",
440
+ "",
441
+ pathsSection,
442
+ ].join("\n"),
443
+ };
444
+ }
445
+
446
+ function buildFixCodePrompt(config, pathsSection) {
447
+ // Run tests and capture output
448
+ console.log(`[fix-code] Running: ${config.testScript}`);
449
+ let testOutput;
450
+ try {
451
+ testOutput = execSync(config.testScript, { cwd: target, encoding: "utf8", timeout: 120000 });
452
+ console.log("[fix-code] Tests pass — nothing to fix.");
453
+ return { systemMessage: "", prompt: null };
454
+ } catch (err) {
455
+ testOutput = `STDOUT:\n${err.stdout || ""}\nSTDERR:\n${err.stderr || ""}`;
456
+ console.log(`[fix-code] Tests failing — ${testOutput.length} chars of output`);
457
+ }
458
+
459
+ const sourceFiles = scanDir(config.sourcePath, [".js", ".ts"], { contentLimit: 2000 });
460
+ const testFiles = scanDir(config.testsPath, [".js", ".ts", ".test.js"], { contentLimit: 2000 });
461
+
462
+ return {
463
+ systemMessage:
464
+ "You are an autonomous coding agent fixing failing tests. Make minimal, targeted changes to fix the test failures.",
465
+ prompt: [
466
+ "## Instructions",
467
+ "Fix the failing tests by modifying the source code.",
468
+ "",
469
+ "## Test Output (failing)",
470
+ "```",
471
+ testOutput.substring(0, 5000),
472
+ "```",
473
+ "",
474
+ `## Source Files (${sourceFiles.length})`,
475
+ ...sourceFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
476
+ "",
477
+ `## Test Files (${testFiles.length})`,
478
+ ...testFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
479
+ "",
480
+ pathsSection,
481
+ "",
482
+ "## Constraints",
483
+ `- Run \`${config.testScript}\` to validate your fixes`,
484
+ "- Make minimal changes to fix the failing tests",
485
+ ].join("\n"),
486
+ };
487
+ }
488
+
489
+ // ─── CLI Tools for Copilot SDK ───────────────────────────────────────
490
+
491
+ function createCliTools(writablePaths, defineTool) {
492
+ const readFile = defineTool("read_file", {
493
+ description: "Read the contents of a file.",
494
+ parameters: {
495
+ type: "object",
496
+ properties: { path: { type: "string", description: "File path to read" } },
497
+ required: ["path"],
498
+ },
499
+ handler: ({ path }) => {
500
+ const resolved = resolve(target, path);
501
+ console.log(` [tool] read_file: ${resolved}`);
502
+ if (!existsSync(resolved)) return { error: `File not found: ${resolved}` };
503
+ try {
504
+ return { content: readFileSync(resolved, "utf8") };
505
+ } catch (err) {
506
+ return { error: err.message };
507
+ }
508
+ },
509
+ });
510
+
511
+ const writeFile = defineTool("write_file", {
512
+ description: "Write content to a file. Parent directories are created automatically.",
513
+ parameters: {
514
+ type: "object",
515
+ properties: {
516
+ path: { type: "string", description: "File path to write" },
517
+ content: { type: "string", description: "Content to write" },
518
+ },
519
+ required: ["path", "content"],
520
+ },
521
+ handler: ({ path, content }) => {
522
+ const resolved = resolve(target, path);
523
+ const isWritable = writablePaths.some((wp) => path.startsWith(wp) || resolved.startsWith(resolve(target, wp)));
524
+ console.log(` [tool] write_file: ${resolved} (${content.length} chars, writable=${isWritable})`);
525
+ if (!isWritable && !dryRun) {
526
+ return { error: `Path not writable: ${path}. Writable: ${writablePaths.join(", ")}` };
527
+ }
528
+ if (dryRun) {
529
+ console.log(` [tool] DRY RUN — would write ${content.length} chars to ${resolved}`);
530
+ return { success: true, dryRun: true };
531
+ }
532
+ try {
533
+ const dir = dirname(resolved);
534
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
535
+ writeFileSync(resolved, content, "utf8");
536
+ return { success: true, path: resolved };
537
+ } catch (err) {
538
+ return { error: err.message };
539
+ }
540
+ },
541
+ });
542
+
543
+ const listFiles = defineTool("list_files", {
544
+ description: "List files and directories at the given path.",
545
+ parameters: {
546
+ type: "object",
547
+ properties: {
548
+ path: { type: "string", description: "Directory path to list" },
549
+ recursive: { type: "boolean", description: "List recursively" },
550
+ },
551
+ required: ["path"],
552
+ },
553
+ handler: ({ path, recursive }) => {
554
+ const resolved = resolve(target, path);
555
+ console.log(` [tool] list_files: ${resolved}`);
556
+ if (!existsSync(resolved)) return { error: `Not found: ${resolved}` };
557
+ try {
558
+ const entries = readdirSync(resolved, { withFileTypes: true, recursive: !!recursive });
559
+ return { files: entries.map((e) => (e.isDirectory() ? `${e.name}/` : e.name)) };
560
+ } catch (err) {
561
+ return { error: err.message };
562
+ }
563
+ },
564
+ });
565
+
566
+ const runCommand = defineTool("run_command", {
567
+ description: "Run a shell command and return stdout/stderr.",
568
+ parameters: {
569
+ type: "object",
570
+ properties: {
571
+ command: { type: "string", description: "Shell command to execute" },
572
+ cwd: { type: "string", description: "Working directory" },
573
+ },
574
+ required: ["command"],
575
+ },
576
+ handler: ({ command: cmd, cwd }) => {
577
+ const workDir = cwd ? resolve(target, cwd) : target;
578
+ console.log(` [tool] run_command: ${cmd} (cwd=${workDir})`);
579
+ const blocked = /\bgit\s+(commit|push|add|reset|checkout|rebase|merge|stash)\b/;
580
+ if (blocked.test(cmd)) {
581
+ console.log(` [tool] BLOCKED git write command: ${cmd}`);
582
+ return { error: "Git write commands are not allowed. Use read_file/write_file tools instead." };
583
+ }
584
+ try {
585
+ const stdout = execSync(cmd, { cwd: workDir, encoding: "utf8", timeout: 120000 });
586
+ return { stdout, exitCode: 0 };
587
+ } catch (err) {
588
+ return { stdout: err.stdout || "", stderr: err.stderr || "", exitCode: err.status || 1 };
589
+ }
590
+ },
591
+ });
592
+
593
+ return [readFile, writeFile, listFiles, runCommand];
594
+ }
595
+
596
+ // ─── Init Runner ─────────────────────────────────────────────────────
597
+
598
+ function initCopyFile(src, dst, label) {
599
+ if (dryRun) {
600
+ console.log(` COPY: ${label}`);
601
+ } else {
602
+ mkdirSync(dirname(dst), { recursive: true });
603
+ copyFileSync(src, dst);
604
+ console.log(` COPY: ${label}`);
605
+ }
606
+ initChanges++;
607
+ }
608
+
609
+ function initCopyDirRecursive(srcPath, dstPath, label, excludes = []) {
610
+ if (!existsSync(srcPath)) {
611
+ console.log(` SKIP: ${label} (not found)`);
612
+ return;
613
+ }
614
+ const entries = readdirSync(srcPath, { withFileTypes: true });
615
+ for (const entry of entries) {
616
+ const srcFull = join(srcPath, entry.name);
617
+ const dstFull = join(dstPath, entry.name);
618
+ const relLabel = `${label}/${entry.name}`;
619
+ if (excludes.some((ex) => entry.name === ex || srcFull.includes(ex))) continue;
620
+ if (entry.isDirectory()) {
621
+ initCopyDirRecursive(srcFull, dstFull, relLabel, excludes);
622
+ } else {
623
+ initCopyFile(srcFull, dstFull, relLabel);
624
+ }
625
+ }
626
+ }
627
+
628
+ function initWorkflows() {
629
+ console.log("--- Workflows ---");
630
+ const workflowsDir = resolve(srcDir, "workflows");
631
+ if (!existsSync(workflowsDir)) return;
632
+ for (const f of readdirSync(workflowsDir)) {
633
+ if (f.endsWith(".yml")) {
634
+ initCopyFile(resolve(workflowsDir, f), resolve(target, ".github/workflows", f), `workflows/${f}`);
635
+ }
636
+ }
637
+ }
638
+
639
+ function initActions(agenticDir) {
640
+ console.log("\n--- Actions ---");
641
+ const actionsDir = resolve(srcDir, "actions");
642
+ if (!existsSync(actionsDir)) return;
643
+ for (const actionName of readdirSync(actionsDir, { withFileTypes: true })) {
644
+ if (actionName.isDirectory()) {
645
+ initCopyDirRecursive(
646
+ resolve(actionsDir, actionName.name),
647
+ resolve(agenticDir, "actions", actionName.name),
648
+ `actions/${actionName.name}`,
649
+ ["node_modules"],
650
+ );
651
+ }
652
+ }
653
+ }
654
+
655
+ function initDirContents(srcSubdir, dstDir, label) {
656
+ console.log(`\n--- ${label} ---`);
657
+ const dir = resolve(srcDir, srcSubdir);
658
+ if (!existsSync(dir)) return;
659
+ for (const f of readdirSync(dir)) {
660
+ initCopyFile(resolve(dir, f), resolve(dstDir, f), `${srcSubdir}/${f}`);
661
+ }
662
+ }
663
+
664
+ function initScripts(agenticDir) {
665
+ console.log("\n--- Scripts ---");
666
+ const scriptsDir = resolve(srcDir, "scripts");
667
+ const DISTRIBUTED_SCRIPTS = [
668
+ "accept-release.sh",
669
+ "activate-schedule.sh",
670
+ "clean.sh",
671
+ "initialise.sh",
672
+ "md-to-html.js",
673
+ "update.sh",
674
+ ];
675
+ if (!existsSync(scriptsDir)) return;
676
+ for (const name of DISTRIBUTED_SCRIPTS) {
677
+ const src = resolve(scriptsDir, name);
678
+ if (existsSync(src)) {
679
+ initCopyFile(src, resolve(agenticDir, "scripts", name), `scripts/${name}`);
680
+ }
681
+ }
682
+ }
683
+
684
+ function initConfig(seedsDir) {
685
+ console.log("\n--- Config ---");
686
+ const tomlSeed = resolve(seedsDir, "zero-agentic-lib.toml");
687
+ const tomlTarget = resolve(target, "agentic-lib.toml");
688
+ if (existsSync(tomlSeed) && !existsSync(tomlTarget)) {
689
+ initCopyFile(tomlSeed, tomlTarget, "agentic-lib.toml (new)");
690
+ } else if (existsSync(tomlTarget)) {
691
+ console.log(" SKIP: agentic-lib.toml already exists");
692
+ } else {
693
+ console.log(" SKIP: seed TOML not found");
694
+ }
695
+ }
696
+
697
+ function initPurge(seedsDir, agenticDir) {
698
+ console.log("\n--- Reset Source Files to Seed State ---");
699
+ const SEED_MAP = {
700
+ "zero-main.js": "src/lib/main.js",
701
+ "zero-main.test.js": "tests/unit/main.test.js",
702
+ "zero-MISSION.md": "MISSION.md",
703
+ "zero-package.json": "package.json",
704
+ "zero-README.md": "README.md",
705
+ };
706
+ for (const [seedFile, targetRel] of Object.entries(SEED_MAP)) {
707
+ const src = resolve(seedsDir, seedFile);
708
+ if (existsSync(src)) {
709
+ initCopyFile(src, resolve(target, targetRel), `SEED: ${seedFile} → ${targetRel}`);
710
+ }
711
+ }
712
+ const intentionFile = resolve(target, "intentïon.md");
713
+ if (existsSync(intentionFile)) {
714
+ if (!dryRun) rmSync(intentionFile);
715
+ console.log(" REMOVE: intentïon.md");
716
+ initChanges++;
717
+ }
718
+ const featuresDir = resolve(agenticDir, "features");
719
+ if (!existsSync(featuresDir)) return;
720
+ for (const f of readdirSync(featuresDir)) {
721
+ if (!dryRun) rmSync(resolve(featuresDir, f));
722
+ console.log(` REMOVE: features/${f}`);
723
+ initChanges++;
724
+ }
725
+ }
726
+
727
+ function runInit() {
728
+ if (!existsSync(target)) {
729
+ console.error(`Target directory does not exist: ${target}`);
730
+ process.exit(1);
731
+ }
732
+ if (!existsSync(srcDir)) {
733
+ console.error(`Source directory not found: ${srcDir}`);
734
+ process.exit(1);
735
+ }
736
+
737
+ const agenticDir = resolve(target, ".github/agentic-lib");
738
+ const seedsDir = resolve(srcDir, "seeds");
739
+ initChanges = 0;
740
+
741
+ console.log("");
742
+ console.log("=== @xn-intenton-z2a/agentic-lib init ===");
743
+ console.log(`Source: ${srcDir}`);
744
+ console.log(`Target: ${target}`);
745
+ console.log(`Purge: ${purge}`);
746
+ console.log(`Mode: ${dryRun ? "DRY RUN" : "LIVE"}`);
747
+ console.log("");
748
+
749
+ initWorkflows();
750
+ initActions(agenticDir);
751
+ initDirContents("agents", resolve(agenticDir, "agents"), "Agents");
752
+ initDirContents("seeds", resolve(agenticDir, "seeds"), "Seeds");
753
+ initScripts(agenticDir);
754
+ initConfig(seedsDir);
755
+ if (purge) initPurge(seedsDir, agenticDir);
756
+
757
+ console.log(`\n${initChanges} change(s)${dryRun ? " (dry run)" : ""}`);
758
+
759
+ if (!dryRun && initChanges > 0) {
760
+ console.log("\nNext steps:");
761
+ if (purge) console.log(` cd ${target} && npm install`);
762
+ console.log(` cd ${resolve(agenticDir, "actions/agentic-step")} && npm ci`);
763
+ console.log(" npm test");
764
+ }
765
+ }