@draht/coding-agent 2026.3.11-1 → 2026.3.14

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 (44) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/bin/draht-tools.cjs +187 -32
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +2 -2
  5. package/dist/config.js.map +1 -1
  6. package/dist/core/package-manager.d.ts.map +1 -1
  7. package/dist/core/package-manager.js +1 -1
  8. package/dist/core/package-manager.js.map +1 -1
  9. package/dist/gsd/domain.d.ts +5 -1
  10. package/dist/gsd/domain.d.ts.map +1 -1
  11. package/dist/gsd/domain.js +71 -1
  12. package/dist/gsd/domain.js.map +1 -1
  13. package/dist/gsd/git.d.ts.map +1 -1
  14. package/dist/gsd/git.js +18 -0
  15. package/dist/gsd/git.js.map +1 -1
  16. package/dist/gsd/index.d.ts +1 -0
  17. package/dist/gsd/index.d.ts.map +1 -1
  18. package/dist/gsd/index.js.map +1 -1
  19. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  20. package/dist/modes/interactive/interactive-mode.js +2 -0
  21. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  22. package/dist/prompts/commands/execute-phase.md +2 -2
  23. package/dist/prompts/commands/fix.md +2 -2
  24. package/dist/prompts/commands/plan-phase.md +5 -1
  25. package/dist/prompts/commands/quick.md +5 -1
  26. package/dist/utils/changelog.d.ts +12 -0
  27. package/dist/utils/changelog.d.ts.map +1 -1
  28. package/dist/utils/changelog.js +25 -14
  29. package/dist/utils/changelog.js.map +1 -1
  30. package/dist/utils/notify.d.ts +12 -0
  31. package/dist/utils/notify.d.ts.map +1 -0
  32. package/dist/utils/notify.js +41 -0
  33. package/dist/utils/notify.js.map +1 -0
  34. package/examples/extensions/antigravity-image-gen.ts +5 -4
  35. package/examples/extensions/custom-provider-gitlab-duo/test.ts +2 -2
  36. package/examples/extensions/notify.ts +9 -2
  37. package/examples/extensions/preset.ts +2 -3
  38. package/examples/extensions/sandbox/index.ts +2 -3
  39. package/examples/extensions/tool-override.ts +2 -3
  40. package/package.json +4 -4
  41. package/prompts/commands/execute-phase.md +2 -2
  42. package/prompts/commands/fix.md +2 -2
  43. package/prompts/commands/plan-phase.md +5 -1
  44. package/prompts/commands/quick.md +5 -1
@@ -41,8 +41,8 @@ Execute this plan. Here is the full plan content:
41
41
  <paste full plan XML here>
42
42
 
43
43
  For each <task> in the plan, follow this TDD cycle:
44
- 1. RED — Write failing tests from <test>. Run the test runner, confirm they FAIL. Commit with: git add <test-files> && git commit -m "red: <description>"
45
- 2. GREEN — Write minimal implementation from <action> to make tests pass. Run tests, confirm PASS. Commit with: git add <files> && git commit -m "green: <task name>"
44
+ 1. RED — Write failing tests from <test>. Run the test runner, confirm they FAIL. Commit with: git add <test-files> && git commit -m "test: <description>"
45
+ 2. GREEN — Write minimal implementation from <action> to make tests pass. Run tests, confirm PASS. Commit with: git add <files> && git commit -m "feat: <task name>"
46
46
  3. REFACTOR — Apply <refactor> improvements if any. Tests must stay green after each change. Commit with: git add <files> && git commit -m "refactor: <description>"
47
47
  4. VERIFY — Run the <verify> step, confirm <done> criteria are met.
48
48
 
@@ -18,12 +18,12 @@ Issue: $ARGUMENTS
18
18
  "Diagnose this issue: $ARGUMENTS. Reproduce the bug by running the relevant test or command. Trace the root cause by reading the code. Identify the exact files and lines involved. Do NOT fix it yet — only report the diagnosis with: root cause, affected files, and a recommended fix approach. Do NOT run draht, draht-tools, or pi commands."
19
19
 
20
20
  2. **Write a reproducing test**: Based on the diagnosis, write a test that demonstrates the bug (it must fail)
21
- - Commit: `draht-tools commit-docs "red: reproduce bug"`
21
+ - Commit: `draht-tools commit-docs "test: reproduce bug"`
22
22
 
23
23
  3. **Minimal fix**: Write the smallest change that makes the test pass
24
24
  - Do not refactor or add features — just fix the bug
25
25
  - Run the full test suite to check for regressions
26
- - Commit: `draht-tools commit-docs "green: fix description"`
26
+ - Commit: `draht-tools commit-docs "fix: fix description"`
27
27
 
28
28
  4. **Refactor** (if needed): Clean up without changing behavior
29
29
  - Tests must stay green after every change
@@ -34,7 +34,11 @@ Phase: $1
34
34
  - Instruction to output the plan as XML (you will save it via `draht-tools create-plan`)
35
35
 
36
36
  6. Collect all plan outputs from subagents
37
- 7. Save plans yourself: `draht-tools create-plan $1 P` for each plan
37
+ 7. Save each plan by piping the subagent's output into `draht-tools create-plan`:
38
+ ```
39
+ echo 'plan content from subagent' | draht-tools create-plan $1 P [title]
40
+ ```
41
+ The content must contain real task details (files, actions, tests) — NOT placeholder brackets. If `create-plan` is called without stdin, it writes a useless template.
38
42
  8. Validate: `draht-tools validate-plans $1`
39
43
  9. Commit: `draht-tools commit-docs "create phase $1 plans"`
40
44
 
@@ -15,7 +15,11 @@ Task: $ARGUMENTS
15
15
 
16
16
  ## Steps
17
17
  1. Run `draht-tools next-quick-number` to get task number
18
- 2. Create quick plan: `draht-tools create-quick-plan NNN "$ARGUMENTS"`
18
+ 2. Analyze the task and write a concrete plan with actual task details (files, actions, verification). Pipe it into `draht-tools create-quick-plan`:
19
+ ```
20
+ echo 'plan content here' | draht-tools create-quick-plan NNN "$ARGUMENTS"
21
+ ```
22
+ The plan content must include: a `# Quick Task NNN: title` heading, a `## Tasks` section with one or more `<task>` XML blocks containing real file paths, real actions, and real verification steps — NOT placeholders like `[files]`.
19
23
  3. **Delegate execution to subagent**: Use the `subagent` tool in **single mode** with the `implementer` agent:
20
24
  "Execute this task: $ARGUMENTS
21
25
 
@@ -2,8 +2,20 @@ export interface ChangelogEntry {
2
2
  major: number;
3
3
  minor: number;
4
4
  patch: number;
5
+ /** Intraday release suffix for date-based versions (e.g., -1, -2) */
6
+ suffix: number;
5
7
  content: string;
6
8
  }
9
+ /**
10
+ * Parse a version string into components.
11
+ * Handles both semver (x.y.z) and date-based versions (YYYY.M.D or YYYY.M.D-N).
12
+ */
13
+ export declare function parseVersion(version: string): {
14
+ major: number;
15
+ minor: number;
16
+ patch: number;
17
+ suffix: number;
18
+ };
7
19
  /**
8
20
  * Parse changelog entries from CHANGELOG.md
9
21
  * Scans for ## lines and collects content until next ## or EOF
@@ -1 +1 @@
1
- {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/utils/changelog.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc,EAAE,CAyDtE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,GAAG,MAAM,CAI9E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc,EAAE,CAW9F;AAGD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC","sourcesContent":["import { existsSync, readFileSync } from \"fs\";\n\nexport interface ChangelogEntry {\n\tmajor: number;\n\tminor: number;\n\tpatch: number;\n\tcontent: string;\n}\n\n/**\n * Parse changelog entries from CHANGELOG.md\n * Scans for ## lines and collects content until next ## or EOF\n */\nexport function parseChangelog(changelogPath: string): ChangelogEntry[] {\n\tif (!existsSync(changelogPath)) {\n\t\treturn [];\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(changelogPath, \"utf-8\");\n\t\tconst lines = content.split(\"\\n\");\n\t\tconst entries: ChangelogEntry[] = [];\n\n\t\tlet currentLines: string[] = [];\n\t\tlet currentVersion: { major: number; minor: number; patch: number } | null = null;\n\n\t\tfor (const line of lines) {\n\t\t\t// Check if this is a version header (## [x.y.z] ...)\n\t\t\tif (line.startsWith(\"## \")) {\n\t\t\t\t// Save previous entry if exists\n\t\t\t\tif (currentVersion && currentLines.length > 0) {\n\t\t\t\t\tentries.push({\n\t\t\t\t\t\t...currentVersion,\n\t\t\t\t\t\tcontent: currentLines.join(\"\\n\").trim(),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Try to parse version from this line\n\t\t\t\tconst versionMatch = line.match(/##\\s+\\[?(\\d+)\\.(\\d+)\\.(\\d+)\\]?/);\n\t\t\t\tif (versionMatch) {\n\t\t\t\t\tcurrentVersion = {\n\t\t\t\t\t\tmajor: Number.parseInt(versionMatch[1], 10),\n\t\t\t\t\t\tminor: Number.parseInt(versionMatch[2], 10),\n\t\t\t\t\t\tpatch: Number.parseInt(versionMatch[3], 10),\n\t\t\t\t\t};\n\t\t\t\t\tcurrentLines = [line];\n\t\t\t\t} else {\n\t\t\t\t\t// Reset if we can't parse version\n\t\t\t\t\tcurrentVersion = null;\n\t\t\t\t\tcurrentLines = [];\n\t\t\t\t}\n\t\t\t} else if (currentVersion) {\n\t\t\t\t// Collect lines for current version\n\t\t\t\tcurrentLines.push(line);\n\t\t\t}\n\t\t}\n\n\t\t// Save last entry\n\t\tif (currentVersion && currentLines.length > 0) {\n\t\t\tentries.push({\n\t\t\t\t...currentVersion,\n\t\t\t\tcontent: currentLines.join(\"\\n\").trim(),\n\t\t\t});\n\t\t}\n\n\t\treturn entries;\n\t} catch (error) {\n\t\tconsole.error(`Warning: Could not parse changelog: ${error}`);\n\t\treturn [];\n\t}\n}\n\n/**\n * Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2\n */\nexport function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number {\n\tif (v1.major !== v2.major) return v1.major - v2.major;\n\tif (v1.minor !== v2.minor) return v1.minor - v2.minor;\n\treturn v1.patch - v2.patch;\n}\n\n/**\n * Get entries newer than lastVersion\n */\nexport function getNewEntries(entries: ChangelogEntry[], lastVersion: string): ChangelogEntry[] {\n\t// Parse lastVersion\n\tconst parts = lastVersion.split(\".\").map(Number);\n\tconst last: ChangelogEntry = {\n\t\tmajor: parts[0] || 0,\n\t\tminor: parts[1] || 0,\n\t\tpatch: parts[2] || 0,\n\t\tcontent: \"\",\n\t};\n\n\treturn entries.filter((entry) => compareVersions(entry, last) > 0);\n}\n\n// Re-export getChangelogPath from paths.ts for convenience\nexport { getChangelogPath } from \"../config.js\";\n"]}
1
+ {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/utils/changelog.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAY7G;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc,EAAE,CAqDtE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,GAAG,MAAM,CAK9E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc,EAAE,CAO9F;AAGD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC","sourcesContent":["import { existsSync, readFileSync } from \"fs\";\n\nexport interface ChangelogEntry {\n\tmajor: number;\n\tminor: number;\n\tpatch: number;\n\t/** Intraday release suffix for date-based versions (e.g., -1, -2) */\n\tsuffix: number;\n\tcontent: string;\n}\n\n/**\n * Parse a version string into components.\n * Handles both semver (x.y.z) and date-based versions (YYYY.M.D or YYYY.M.D-N).\n */\nexport function parseVersion(version: string): { major: number; minor: number; patch: number; suffix: number } {\n\t// Match YYYY.M.D-N or YYYY.M.D or x.y.z\n\tconst match = version.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\d+))?/);\n\tif (!match) {\n\t\treturn { major: 0, minor: 0, patch: 0, suffix: 0 };\n\t}\n\treturn {\n\t\tmajor: Number.parseInt(match[1], 10),\n\t\tminor: Number.parseInt(match[2], 10),\n\t\tpatch: Number.parseInt(match[3], 10),\n\t\tsuffix: match[4] ? Number.parseInt(match[4], 10) : 0,\n\t};\n}\n\n/**\n * Parse changelog entries from CHANGELOG.md\n * Scans for ## lines and collects content until next ## or EOF\n */\nexport function parseChangelog(changelogPath: string): ChangelogEntry[] {\n\tif (!existsSync(changelogPath)) {\n\t\treturn [];\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(changelogPath, \"utf-8\");\n\t\tconst lines = content.split(\"\\n\");\n\t\tconst entries: ChangelogEntry[] = [];\n\n\t\tlet currentLines: string[] = [];\n\t\tlet currentVersion: { major: number; minor: number; patch: number; suffix: number } | null = null;\n\n\t\tfor (const line of lines) {\n\t\t\t// Check if this is a version header (## [x.y.z] or ## [YYYY.M.D-N] ...)\n\t\t\tif (line.startsWith(\"## \")) {\n\t\t\t\t// Save previous entry if exists\n\t\t\t\tif (currentVersion && currentLines.length > 0) {\n\t\t\t\t\tentries.push({\n\t\t\t\t\t\t...currentVersion,\n\t\t\t\t\t\tcontent: currentLines.join(\"\\n\").trim(),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Try to parse version from this line (supports YYYY.M.D-N format)\n\t\t\t\tconst versionMatch = line.match(/##\\s+\\[?(\\d+\\.\\d+\\.\\d+(?:-\\d+)?)\\]?/);\n\t\t\t\tif (versionMatch) {\n\t\t\t\t\tcurrentVersion = parseVersion(versionMatch[1]);\n\t\t\t\t\tcurrentLines = [line];\n\t\t\t\t} else {\n\t\t\t\t\t// Reset if we can't parse version\n\t\t\t\t\tcurrentVersion = null;\n\t\t\t\t\tcurrentLines = [];\n\t\t\t\t}\n\t\t\t} else if (currentVersion) {\n\t\t\t\t// Collect lines for current version\n\t\t\t\tcurrentLines.push(line);\n\t\t\t}\n\t\t}\n\n\t\t// Save last entry\n\t\tif (currentVersion && currentLines.length > 0) {\n\t\t\tentries.push({\n\t\t\t\t...currentVersion,\n\t\t\t\tcontent: currentLines.join(\"\\n\").trim(),\n\t\t\t});\n\t\t}\n\n\t\treturn entries;\n\t} catch (error) {\n\t\tconsole.error(`Warning: Could not parse changelog: ${error}`);\n\t\treturn [];\n\t}\n}\n\n/**\n * Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2\n */\nexport function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number {\n\tif (v1.major !== v2.major) return v1.major - v2.major;\n\tif (v1.minor !== v2.minor) return v1.minor - v2.minor;\n\tif (v1.patch !== v2.patch) return v1.patch - v2.patch;\n\treturn v1.suffix - v2.suffix;\n}\n\n/**\n * Get entries newer than lastVersion\n */\nexport function getNewEntries(entries: ChangelogEntry[], lastVersion: string): ChangelogEntry[] {\n\tconst last: ChangelogEntry = {\n\t\t...parseVersion(lastVersion),\n\t\tcontent: \"\",\n\t};\n\n\treturn entries.filter((entry) => compareVersions(entry, last) > 0);\n}\n\n// Re-export getChangelogPath from paths.ts for convenience\nexport { getChangelogPath } from \"../config.js\";\n"]}
@@ -1,4 +1,21 @@
1
1
  import { existsSync, readFileSync } from "fs";
2
+ /**
3
+ * Parse a version string into components.
4
+ * Handles both semver (x.y.z) and date-based versions (YYYY.M.D or YYYY.M.D-N).
5
+ */
6
+ export function parseVersion(version) {
7
+ // Match YYYY.M.D-N or YYYY.M.D or x.y.z
8
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(\d+))?/);
9
+ if (!match) {
10
+ return { major: 0, minor: 0, patch: 0, suffix: 0 };
11
+ }
12
+ return {
13
+ major: Number.parseInt(match[1], 10),
14
+ minor: Number.parseInt(match[2], 10),
15
+ patch: Number.parseInt(match[3], 10),
16
+ suffix: match[4] ? Number.parseInt(match[4], 10) : 0,
17
+ };
18
+ }
2
19
  /**
3
20
  * Parse changelog entries from CHANGELOG.md
4
21
  * Scans for ## lines and collects content until next ## or EOF
@@ -14,7 +31,7 @@ export function parseChangelog(changelogPath) {
14
31
  let currentLines = [];
15
32
  let currentVersion = null;
16
33
  for (const line of lines) {
17
- // Check if this is a version header (## [x.y.z] ...)
34
+ // Check if this is a version header (## [x.y.z] or ## [YYYY.M.D-N] ...)
18
35
  if (line.startsWith("## ")) {
19
36
  // Save previous entry if exists
20
37
  if (currentVersion && currentLines.length > 0) {
@@ -23,14 +40,10 @@ export function parseChangelog(changelogPath) {
23
40
  content: currentLines.join("\n").trim(),
24
41
  });
25
42
  }
26
- // Try to parse version from this line
27
- const versionMatch = line.match(/##\s+\[?(\d+)\.(\d+)\.(\d+)\]?/);
43
+ // Try to parse version from this line (supports YYYY.M.D-N format)
44
+ const versionMatch = line.match(/##\s+\[?(\d+\.\d+\.\d+(?:-\d+)?)\]?/);
28
45
  if (versionMatch) {
29
- currentVersion = {
30
- major: Number.parseInt(versionMatch[1], 10),
31
- minor: Number.parseInt(versionMatch[2], 10),
32
- patch: Number.parseInt(versionMatch[3], 10),
33
- };
46
+ currentVersion = parseVersion(versionMatch[1]);
34
47
  currentLines = [line];
35
48
  }
36
49
  else {
@@ -66,18 +79,16 @@ export function compareVersions(v1, v2) {
66
79
  return v1.major - v2.major;
67
80
  if (v1.minor !== v2.minor)
68
81
  return v1.minor - v2.minor;
69
- return v1.patch - v2.patch;
82
+ if (v1.patch !== v2.patch)
83
+ return v1.patch - v2.patch;
84
+ return v1.suffix - v2.suffix;
70
85
  }
71
86
  /**
72
87
  * Get entries newer than lastVersion
73
88
  */
74
89
  export function getNewEntries(entries, lastVersion) {
75
- // Parse lastVersion
76
- const parts = lastVersion.split(".").map(Number);
77
90
  const last = {
78
- major: parts[0] || 0,
79
- minor: parts[1] || 0,
80
- patch: parts[2] || 0,
91
+ ...parseVersion(lastVersion),
81
92
  content: "",
82
93
  };
83
94
  return entries.filter((entry) => compareVersions(entry, last) > 0);
@@ -1 +1 @@
1
- {"version":3,"file":"changelog.js","sourceRoot":"","sources":["../../src/utils/changelog.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"fs\";\n\nexport interface ChangelogEntry {\n\tmajor: number;\n\tminor: number;\n\tpatch: number;\n\tcontent: string;\n}\n\n/**\n * Parse changelog entries from CHANGELOG.md\n * Scans for ## lines and collects content until next ## or EOF\n */\nexport function parseChangelog(changelogPath: string): ChangelogEntry[] {\n\tif (!existsSync(changelogPath)) {\n\t\treturn [];\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(changelogPath, \"utf-8\");\n\t\tconst lines = content.split(\"\\n\");\n\t\tconst entries: ChangelogEntry[] = [];\n\n\t\tlet currentLines: string[] = [];\n\t\tlet currentVersion: { major: number; minor: number; patch: number } | null = null;\n\n\t\tfor (const line of lines) {\n\t\t\t// Check if this is a version header (## [x.y.z] ...)\n\t\t\tif (line.startsWith(\"## \")) {\n\t\t\t\t// Save previous entry if exists\n\t\t\t\tif (currentVersion && currentLines.length > 0) {\n\t\t\t\t\tentries.push({\n\t\t\t\t\t\t...currentVersion,\n\t\t\t\t\t\tcontent: currentLines.join(\"\\n\").trim(),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Try to parse version from this line\n\t\t\t\tconst versionMatch = line.match(/##\\s+\\[?(\\d+)\\.(\\d+)\\.(\\d+)\\]?/);\n\t\t\t\tif (versionMatch) {\n\t\t\t\t\tcurrentVersion = {\n\t\t\t\t\t\tmajor: Number.parseInt(versionMatch[1], 10),\n\t\t\t\t\t\tminor: Number.parseInt(versionMatch[2], 10),\n\t\t\t\t\t\tpatch: Number.parseInt(versionMatch[3], 10),\n\t\t\t\t\t};\n\t\t\t\t\tcurrentLines = [line];\n\t\t\t\t} else {\n\t\t\t\t\t// Reset if we can't parse version\n\t\t\t\t\tcurrentVersion = null;\n\t\t\t\t\tcurrentLines = [];\n\t\t\t\t}\n\t\t\t} else if (currentVersion) {\n\t\t\t\t// Collect lines for current version\n\t\t\t\tcurrentLines.push(line);\n\t\t\t}\n\t\t}\n\n\t\t// Save last entry\n\t\tif (currentVersion && currentLines.length > 0) {\n\t\t\tentries.push({\n\t\t\t\t...currentVersion,\n\t\t\t\tcontent: currentLines.join(\"\\n\").trim(),\n\t\t\t});\n\t\t}\n\n\t\treturn entries;\n\t} catch (error) {\n\t\tconsole.error(`Warning: Could not parse changelog: ${error}`);\n\t\treturn [];\n\t}\n}\n\n/**\n * Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2\n */\nexport function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number {\n\tif (v1.major !== v2.major) return v1.major - v2.major;\n\tif (v1.minor !== v2.minor) return v1.minor - v2.minor;\n\treturn v1.patch - v2.patch;\n}\n\n/**\n * Get entries newer than lastVersion\n */\nexport function getNewEntries(entries: ChangelogEntry[], lastVersion: string): ChangelogEntry[] {\n\t// Parse lastVersion\n\tconst parts = lastVersion.split(\".\").map(Number);\n\tconst last: ChangelogEntry = {\n\t\tmajor: parts[0] || 0,\n\t\tminor: parts[1] || 0,\n\t\tpatch: parts[2] || 0,\n\t\tcontent: \"\",\n\t};\n\n\treturn entries.filter((entry) => compareVersions(entry, last) > 0);\n}\n\n// Re-export getChangelogPath from paths.ts for convenience\nexport { getChangelogPath } from \"../config.js\";\n"]}
1
+ {"version":3,"file":"changelog.js","sourceRoot":"","sources":["../../src/utils/changelog.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"fs\";\n\nexport interface ChangelogEntry {\n\tmajor: number;\n\tminor: number;\n\tpatch: number;\n\t/** Intraday release suffix for date-based versions (e.g., -1, -2) */\n\tsuffix: number;\n\tcontent: string;\n}\n\n/**\n * Parse a version string into components.\n * Handles both semver (x.y.z) and date-based versions (YYYY.M.D or YYYY.M.D-N).\n */\nexport function parseVersion(version: string): { major: number; minor: number; patch: number; suffix: number } {\n\t// Match YYYY.M.D-N or YYYY.M.D or x.y.z\n\tconst match = version.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\d+))?/);\n\tif (!match) {\n\t\treturn { major: 0, minor: 0, patch: 0, suffix: 0 };\n\t}\n\treturn {\n\t\tmajor: Number.parseInt(match[1], 10),\n\t\tminor: Number.parseInt(match[2], 10),\n\t\tpatch: Number.parseInt(match[3], 10),\n\t\tsuffix: match[4] ? Number.parseInt(match[4], 10) : 0,\n\t};\n}\n\n/**\n * Parse changelog entries from CHANGELOG.md\n * Scans for ## lines and collects content until next ## or EOF\n */\nexport function parseChangelog(changelogPath: string): ChangelogEntry[] {\n\tif (!existsSync(changelogPath)) {\n\t\treturn [];\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(changelogPath, \"utf-8\");\n\t\tconst lines = content.split(\"\\n\");\n\t\tconst entries: ChangelogEntry[] = [];\n\n\t\tlet currentLines: string[] = [];\n\t\tlet currentVersion: { major: number; minor: number; patch: number; suffix: number } | null = null;\n\n\t\tfor (const line of lines) {\n\t\t\t// Check if this is a version header (## [x.y.z] or ## [YYYY.M.D-N] ...)\n\t\t\tif (line.startsWith(\"## \")) {\n\t\t\t\t// Save previous entry if exists\n\t\t\t\tif (currentVersion && currentLines.length > 0) {\n\t\t\t\t\tentries.push({\n\t\t\t\t\t\t...currentVersion,\n\t\t\t\t\t\tcontent: currentLines.join(\"\\n\").trim(),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Try to parse version from this line (supports YYYY.M.D-N format)\n\t\t\t\tconst versionMatch = line.match(/##\\s+\\[?(\\d+\\.\\d+\\.\\d+(?:-\\d+)?)\\]?/);\n\t\t\t\tif (versionMatch) {\n\t\t\t\t\tcurrentVersion = parseVersion(versionMatch[1]);\n\t\t\t\t\tcurrentLines = [line];\n\t\t\t\t} else {\n\t\t\t\t\t// Reset if we can't parse version\n\t\t\t\t\tcurrentVersion = null;\n\t\t\t\t\tcurrentLines = [];\n\t\t\t\t}\n\t\t\t} else if (currentVersion) {\n\t\t\t\t// Collect lines for current version\n\t\t\t\tcurrentLines.push(line);\n\t\t\t}\n\t\t}\n\n\t\t// Save last entry\n\t\tif (currentVersion && currentLines.length > 0) {\n\t\t\tentries.push({\n\t\t\t\t...currentVersion,\n\t\t\t\tcontent: currentLines.join(\"\\n\").trim(),\n\t\t\t});\n\t\t}\n\n\t\treturn entries;\n\t} catch (error) {\n\t\tconsole.error(`Warning: Could not parse changelog: ${error}`);\n\t\treturn [];\n\t}\n}\n\n/**\n * Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2\n */\nexport function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number {\n\tif (v1.major !== v2.major) return v1.major - v2.major;\n\tif (v1.minor !== v2.minor) return v1.minor - v2.minor;\n\tif (v1.patch !== v2.patch) return v1.patch - v2.patch;\n\treturn v1.suffix - v2.suffix;\n}\n\n/**\n * Get entries newer than lastVersion\n */\nexport function getNewEntries(entries: ChangelogEntry[], lastVersion: string): ChangelogEntry[] {\n\tconst last: ChangelogEntry = {\n\t\t...parseVersion(lastVersion),\n\t\tcontent: \"\",\n\t};\n\n\treturn entries.filter((entry) => compareVersions(entry, last) > 0);\n}\n\n// Re-export getChangelogPath from paths.ts for convenience\nexport { getChangelogPath } from \"../config.js\";\n"]}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Terminal notification utility.
3
+ *
4
+ * Sends a native notification when the agent finishes its turn.
5
+ * Detection order:
6
+ * - cmux: `cmux notify` CLI
7
+ * - Windows Terminal (WSL): PowerShell toast
8
+ * - Kitty: OSC 99
9
+ * - Fallback: OSC 777 (Ghostty, iTerm2, WezTerm, rxvt-unicode)
10
+ */
11
+ export declare function sendTerminalNotification(title: string, body: string): void;
12
+ //# sourceMappingURL=notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/utils/notify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiBH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAa1E","sourcesContent":["/**\n * Terminal notification utility.\n *\n * Sends a native notification when the agent finishes its turn.\n * Detection order:\n * - cmux: `cmux notify` CLI\n * - Windows Terminal (WSL): PowerShell toast\n * - Kitty: OSC 99\n * - Fallback: OSC 777 (Ghostty, iTerm2, WezTerm, rxvt-unicode)\n */\n\nimport { execFile } from \"child_process\";\n\nfunction windowsToastScript(title: string, body: string): string {\n\tconst type = \"Windows.UI.Notifications\";\n\tconst mgr = `[${type}.ToastNotificationManager, ${type}, ContentType = WindowsRuntime]`;\n\tconst template = `[${type}.ToastTemplateType]::ToastText01`;\n\tconst toast = `[${type}.ToastNotification]::new($xml)`;\n\treturn [\n\t\t`${mgr} > $null`,\n\t\t`$xml = [${type}.ToastNotificationManager]::GetTemplateContent(${template})`,\n\t\t`$xml.GetElementsByTagName('text')[0].AppendChild($xml.CreateTextNode('${body}')) > $null`,\n\t\t`[${type}.ToastNotificationManager]::CreateToastNotifier('${title}').Show(${toast})`,\n\t].join(\"; \");\n}\n\nexport function sendTerminalNotification(title: string, body: string): void {\n\tif (process.env.CMUX_BUNDLE_ID) {\n\t\texecFile(\"cmux\", [\"notify\", \"--title\", title, \"--body\", body]);\n\t} else if (process.env.WT_SESSION) {\n\t\texecFile(\"powershell.exe\", [\"-NoProfile\", \"-Command\", windowsToastScript(title, body)]);\n\t} else if (process.env.KITTY_WINDOW_ID) {\n\t\t// Kitty OSC 99\n\t\tprocess.stdout.write(`\\x1b]99;i=1:d=0;${title}\\x1b\\\\`);\n\t\tprocess.stdout.write(`\\x1b]99;i=1:p=body;${body}\\x1b\\\\`);\n\t} else {\n\t\t// OSC 777 (Ghostty, iTerm2, WezTerm, rxvt-unicode)\n\t\tprocess.stdout.write(`\\x1b]777;notify;${title};${body}\\x07`);\n\t}\n}\n"]}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Terminal notification utility.
3
+ *
4
+ * Sends a native notification when the agent finishes its turn.
5
+ * Detection order:
6
+ * - cmux: `cmux notify` CLI
7
+ * - Windows Terminal (WSL): PowerShell toast
8
+ * - Kitty: OSC 99
9
+ * - Fallback: OSC 777 (Ghostty, iTerm2, WezTerm, rxvt-unicode)
10
+ */
11
+ import { execFile } from "child_process";
12
+ function windowsToastScript(title, body) {
13
+ const type = "Windows.UI.Notifications";
14
+ const mgr = `[${type}.ToastNotificationManager, ${type}, ContentType = WindowsRuntime]`;
15
+ const template = `[${type}.ToastTemplateType]::ToastText01`;
16
+ const toast = `[${type}.ToastNotification]::new($xml)`;
17
+ return [
18
+ `${mgr} > $null`,
19
+ `$xml = [${type}.ToastNotificationManager]::GetTemplateContent(${template})`,
20
+ `$xml.GetElementsByTagName('text')[0].AppendChild($xml.CreateTextNode('${body}')) > $null`,
21
+ `[${type}.ToastNotificationManager]::CreateToastNotifier('${title}').Show(${toast})`,
22
+ ].join("; ");
23
+ }
24
+ export function sendTerminalNotification(title, body) {
25
+ if (process.env.CMUX_BUNDLE_ID) {
26
+ execFile("cmux", ["notify", "--title", title, "--body", body]);
27
+ }
28
+ else if (process.env.WT_SESSION) {
29
+ execFile("powershell.exe", ["-NoProfile", "-Command", windowsToastScript(title, body)]);
30
+ }
31
+ else if (process.env.KITTY_WINDOW_ID) {
32
+ // Kitty OSC 99
33
+ process.stdout.write(`\x1b]99;i=1:d=0;${title}\x1b\\`);
34
+ process.stdout.write(`\x1b]99;i=1:p=body;${body}\x1b\\`);
35
+ }
36
+ else {
37
+ // OSC 777 (Ghostty, iTerm2, WezTerm, rxvt-unicode)
38
+ process.stdout.write(`\x1b]777;notify;${title};${body}\x07`);
39
+ }
40
+ }
41
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/utils/notify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,SAAS,kBAAkB,CAAC,KAAa,EAAE,IAAY,EAAU;IAChE,MAAM,IAAI,GAAG,0BAA0B,CAAC;IACxC,MAAM,GAAG,GAAG,IAAI,IAAI,8BAA8B,IAAI,iCAAiC,CAAC;IACxF,MAAM,QAAQ,GAAG,IAAI,IAAI,kCAAkC,CAAC;IAC5D,MAAM,KAAK,GAAG,IAAI,IAAI,gCAAgC,CAAC;IACvD,OAAO;QACN,GAAG,GAAG,UAAU;QAChB,WAAW,IAAI,kDAAkD,QAAQ,GAAG;QAC5E,yEAAyE,IAAI,aAAa;QAC1F,IAAI,IAAI,oDAAoD,KAAK,WAAW,KAAK,GAAG;KACpF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACb;AAED,MAAM,UAAU,wBAAwB,CAAC,KAAa,EAAE,IAAY,EAAQ;IAC3E,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;QAChC,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;IAChE,CAAC;SAAM,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;QACnC,QAAQ,CAAC,gBAAgB,EAAE,CAAC,YAAY,EAAE,UAAU,EAAE,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACzF,CAAC;SAAM,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;QACxC,eAAe;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,KAAK,QAAQ,CAAC,CAAC;QACvD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,IAAI,QAAQ,CAAC,CAAC;IAC1D,CAAC;SAAM,CAAC;QACP,mDAAmD;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC;IAC9D,CAAC;AAAA,CACD","sourcesContent":["/**\n * Terminal notification utility.\n *\n * Sends a native notification when the agent finishes its turn.\n * Detection order:\n * - cmux: `cmux notify` CLI\n * - Windows Terminal (WSL): PowerShell toast\n * - Kitty: OSC 99\n * - Fallback: OSC 777 (Ghostty, iTerm2, WezTerm, rxvt-unicode)\n */\n\nimport { execFile } from \"child_process\";\n\nfunction windowsToastScript(title: string, body: string): string {\n\tconst type = \"Windows.UI.Notifications\";\n\tconst mgr = `[${type}.ToastNotificationManager, ${type}, ContentType = WindowsRuntime]`;\n\tconst template = `[${type}.ToastTemplateType]::ToastText01`;\n\tconst toast = `[${type}.ToastNotification]::new($xml)`;\n\treturn [\n\t\t`${mgr} > $null`,\n\t\t`$xml = [${type}.ToastNotificationManager]::GetTemplateContent(${template})`,\n\t\t`$xml.GetElementsByTagName('text')[0].AppendChild($xml.CreateTextNode('${body}')) > $null`,\n\t\t`[${type}.ToastNotificationManager]::CreateToastNotifier('${title}').Show(${toast})`,\n\t].join(\"; \");\n}\n\nexport function sendTerminalNotification(title: string, body: string): void {\n\tif (process.env.CMUX_BUNDLE_ID) {\n\t\texecFile(\"cmux\", [\"notify\", \"--title\", title, \"--body\", body]);\n\t} else if (process.env.WT_SESSION) {\n\t\texecFile(\"powershell.exe\", [\"-NoProfile\", \"-Command\", windowsToastScript(title, body)]);\n\t} else if (process.env.KITTY_WINDOW_ID) {\n\t\t// Kitty OSC 99\n\t\tprocess.stdout.write(`\\x1b]99;i=1:d=0;${title}\\x1b\\\\`);\n\t\tprocess.stdout.write(`\\x1b]99;i=1:p=body;${body}\\x1b\\\\`);\n\t} else {\n\t\t// OSC 777 (Ghostty, iTerm2, WezTerm, rxvt-unicode)\n\t\tprocess.stdout.write(`\\x1b]777;notify;${title};${body}\\x07`);\n\t}\n}\n"]}
@@ -28,10 +28,9 @@
28
28
  import { randomUUID } from "node:crypto";
29
29
  import { existsSync, readFileSync } from "node:fs";
30
30
  import { mkdir, writeFile } from "node:fs/promises";
31
- import { homedir } from "node:os";
32
31
  import { join } from "node:path";
33
32
  import { StringEnum } from "@draht/ai";
34
- import type { ExtensionAPI } from "@draht/coding-agent";
33
+ import { type ExtensionAPI, getAgentDir } from "@draht/coding-agent";
35
34
  import { type Static, Type } from "@sinclair/typebox";
36
35
 
37
36
  const PROVIDER = "google-antigravity";
@@ -184,7 +183,8 @@ function readConfigFile(path: string): ExtensionConfig {
184
183
  }
185
184
 
186
185
  function loadConfig(cwd: string): ExtensionConfig {
187
- const globalConfig = readConfigFile(join(homedir(), ".pi", "agent", "extensions", "antigravity-image-gen.json"));
186
+ const globalPath = join(getAgentDir(), "extensions", "antigravity-image-gen.json");
187
+ const globalConfig = readConfigFile(globalPath);
188
188
  const projectConfig = readConfigFile(join(cwd, ".pi", "extensions", "antigravity-image-gen.json"));
189
189
  return { ...globalConfig, ...projectConfig };
190
190
  }
@@ -204,7 +204,8 @@ function resolveSaveConfig(params: ToolParams, cwd: string): SaveConfig {
204
204
  }
205
205
 
206
206
  if (mode === "global") {
207
- return { mode, outputDir: join(homedir(), ".pi", "agent", "generated-images") };
207
+ const outputDir = join(getAgentDir(), "generated-images");
208
+ return { mode, outputDir };
208
209
  }
209
210
 
210
211
  if (mode === "custom") {
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { type Api, type Context, type Model, registerApiProvider, streamSimple } from "@draht/ai";
12
12
  import { readFileSync } from "fs";
13
- import { homedir } from "os";
13
+ import { getAgentDir } from "packages/coding-agent/src/config.js";
14
14
  import { join } from "path";
15
15
  import { MODELS, streamGitLabDuo } from "./index.js";
16
16
 
@@ -28,7 +28,7 @@ async function main() {
28
28
  }
29
29
 
30
30
  // Read auth
31
- const authPath = join(homedir(), ".pi", "agent", "auth.json");
31
+ const authPath = join(getAgentDir(), "extensions", "auth.json");
32
32
  const authData = JSON.parse(readFileSync(authPath, "utf-8"));
33
33
  const gitlabCred = authData["gitlab-duo"];
34
34
  if (!gitlabCred?.access) {
@@ -3,12 +3,14 @@
3
3
  *
4
4
  * Sends a native terminal notification when Pi agent is done and waiting for input.
5
5
  * Supports multiple terminal protocols:
6
+ * - cmux: uses `cmux notify` CLI
6
7
  * - OSC 777: Ghostty, iTerm2, WezTerm, rxvt-unicode
7
8
  * - OSC 99: Kitty
8
9
  * - Windows toast: Windows Terminal (WSL)
9
10
  */
10
11
 
11
12
  import type { ExtensionAPI } from "@draht/coding-agent";
13
+ import { execFile } from "child_process";
12
14
 
13
15
  function windowsToastScript(title: string, body: string): string {
14
16
  const type = "Windows.UI.Notifications";
@@ -33,13 +35,18 @@ function notifyOSC99(title: string, body: string): void {
33
35
  process.stdout.write(`\x1b]99;i=1:p=body;${body}\x1b\\`);
34
36
  }
35
37
 
38
+ function notifyCmux(title: string, body: string): void {
39
+ execFile("cmux", ["notify", "--title", title, "--body", body]);
40
+ }
41
+
36
42
  function notifyWindows(title: string, body: string): void {
37
- const { execFile } = require("child_process");
38
43
  execFile("powershell.exe", ["-NoProfile", "-Command", windowsToastScript(title, body)]);
39
44
  }
40
45
 
41
46
  function notify(title: string, body: string): void {
42
- if (process.env.WT_SESSION) {
47
+ if (process.env.CMUX_BUNDLE_ID) {
48
+ notifyCmux(title, body);
49
+ } else if (process.env.WT_SESSION) {
43
50
  notifyWindows(title, body);
44
51
  } else if (process.env.KITTY_WINDOW_ID) {
45
52
  notifyOSC99(title, body);
@@ -39,10 +39,9 @@
39
39
  */
40
40
 
41
41
  import { existsSync, readFileSync } from "node:fs";
42
- import { homedir } from "node:os";
43
42
  import { join } from "node:path";
44
43
  import type { ExtensionAPI, ExtensionContext } from "@draht/coding-agent";
45
- import { DynamicBorder } from "@draht/coding-agent";
44
+ import { DynamicBorder, getAgentDir } from "@draht/coding-agent";
46
45
  import { Container, Key, type SelectItem, SelectList, Text } from "@draht/tui";
47
46
 
48
47
  // Preset configuration
@@ -68,7 +67,7 @@ interface PresetsConfig {
68
67
  * Project-local presets override global presets with the same name.
69
68
  */
70
69
  function loadPresets(cwd: string): PresetsConfig {
71
- const globalPath = join(homedir(), ".pi", "agent", "presets.json");
70
+ const globalPath = join(getAgentDir(), "presets.json");
72
71
  const projectPath = join(cwd, ".pi", "presets.json");
73
72
 
74
73
  let globalPresets: PresetsConfig = {};
@@ -39,11 +39,10 @@
39
39
 
40
40
  import { spawn } from "node:child_process";
41
41
  import { existsSync, readFileSync } from "node:fs";
42
- import { homedir } from "node:os";
43
42
  import { join } from "node:path";
44
43
  import { SandboxManager, type SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
45
44
  import type { ExtensionAPI } from "@draht/coding-agent";
46
- import { type BashOperations, createBashTool } from "@draht/coding-agent";
45
+ import { type BashOperations, createBashTool, getAgentDir } from "@draht/coding-agent";
47
46
 
48
47
  interface SandboxConfig extends SandboxRuntimeConfig {
49
48
  enabled?: boolean;
@@ -75,7 +74,7 @@ const DEFAULT_CONFIG: SandboxConfig = {
75
74
 
76
75
  function loadConfig(cwd: string): SandboxConfig {
77
76
  const projectConfigPath = join(cwd, ".pi", "sandbox.json");
78
- const globalConfigPath = join(homedir(), ".pi", "agent", "sandbox.json");
77
+ const globalConfigPath = join(getAgentDir(), "extensions", "sandbox.json");
79
78
 
80
79
  let globalConfig: Partial<SandboxConfig> = {};
81
80
  let projectConfig: Partial<SandboxConfig> = {};
@@ -21,14 +21,13 @@
21
21
  */
22
22
 
23
23
  import type { TextContent } from "@draht/ai";
24
- import type { ExtensionAPI } from "@draht/coding-agent";
24
+ import { type ExtensionAPI, getAgentDir } from "@draht/coding-agent";
25
25
  import { Type } from "@sinclair/typebox";
26
26
  import { appendFileSync, constants, readFileSync } from "fs";
27
27
  import { access, readFile } from "fs/promises";
28
- import { homedir } from "os";
29
28
  import { join, resolve } from "path";
30
29
 
31
- const LOG_FILE = join(homedir(), ".pi", "agent", "read-access.log");
30
+ const LOG_FILE = join(getAgentDir(), "read-access.log");
32
31
 
33
32
  // Paths that are blocked from reading
34
33
  const BLOCKED_PATTERNS = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@draht/coding-agent",
3
- "version": "2026.3.11-1",
3
+ "version": "2026.3.14",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "drahtConfig": {
@@ -48,9 +48,9 @@
48
48
  "@mariozechner/jiti": "^2.6.2",
49
49
  "@sinclair/typebox": "^0.34.41",
50
50
  "ajv": "^8.17.1",
51
- "@draht/agent-core": "2026.3.11-1",
52
- "@draht/ai": "2026.3.11-1",
53
- "@draht/tui": "2026.3.11-1",
51
+ "@draht/agent-core": "2026.3.14",
52
+ "@draht/ai": "2026.3.14",
53
+ "@draht/tui": "2026.3.14",
54
54
  "@silvia-odwyer/photon-node": "^0.3.4",
55
55
  "chalk": "^5.5.0",
56
56
  "cli-highlight": "^2.1.11",
@@ -41,8 +41,8 @@ Execute this plan. Here is the full plan content:
41
41
  <paste full plan XML here>
42
42
 
43
43
  For each <task> in the plan, follow this TDD cycle:
44
- 1. RED — Write failing tests from <test>. Run the test runner, confirm they FAIL. Commit with: git add <test-files> && git commit -m "red: <description>"
45
- 2. GREEN — Write minimal implementation from <action> to make tests pass. Run tests, confirm PASS. Commit with: git add <files> && git commit -m "green: <task name>"
44
+ 1. RED — Write failing tests from <test>. Run the test runner, confirm they FAIL. Commit with: git add <test-files> && git commit -m "test: <description>"
45
+ 2. GREEN — Write minimal implementation from <action> to make tests pass. Run tests, confirm PASS. Commit with: git add <files> && git commit -m "feat: <task name>"
46
46
  3. REFACTOR — Apply <refactor> improvements if any. Tests must stay green after each change. Commit with: git add <files> && git commit -m "refactor: <description>"
47
47
  4. VERIFY — Run the <verify> step, confirm <done> criteria are met.
48
48
 
@@ -18,12 +18,12 @@ Issue: $ARGUMENTS
18
18
  "Diagnose this issue: $ARGUMENTS. Reproduce the bug by running the relevant test or command. Trace the root cause by reading the code. Identify the exact files and lines involved. Do NOT fix it yet — only report the diagnosis with: root cause, affected files, and a recommended fix approach. Do NOT run draht, draht-tools, or pi commands."
19
19
 
20
20
  2. **Write a reproducing test**: Based on the diagnosis, write a test that demonstrates the bug (it must fail)
21
- - Commit: `draht-tools commit-docs "red: reproduce bug"`
21
+ - Commit: `draht-tools commit-docs "test: reproduce bug"`
22
22
 
23
23
  3. **Minimal fix**: Write the smallest change that makes the test pass
24
24
  - Do not refactor or add features — just fix the bug
25
25
  - Run the full test suite to check for regressions
26
- - Commit: `draht-tools commit-docs "green: fix description"`
26
+ - Commit: `draht-tools commit-docs "fix: fix description"`
27
27
 
28
28
  4. **Refactor** (if needed): Clean up without changing behavior
29
29
  - Tests must stay green after every change
@@ -34,7 +34,11 @@ Phase: $1
34
34
  - Instruction to output the plan as XML (you will save it via `draht-tools create-plan`)
35
35
 
36
36
  6. Collect all plan outputs from subagents
37
- 7. Save plans yourself: `draht-tools create-plan $1 P` for each plan
37
+ 7. Save each plan by piping the subagent's output into `draht-tools create-plan`:
38
+ ```
39
+ echo 'plan content from subagent' | draht-tools create-plan $1 P [title]
40
+ ```
41
+ The content must contain real task details (files, actions, tests) — NOT placeholder brackets. If `create-plan` is called without stdin, it writes a useless template.
38
42
  8. Validate: `draht-tools validate-plans $1`
39
43
  9. Commit: `draht-tools commit-docs "create phase $1 plans"`
40
44
 
@@ -15,7 +15,11 @@ Task: $ARGUMENTS
15
15
 
16
16
  ## Steps
17
17
  1. Run `draht-tools next-quick-number` to get task number
18
- 2. Create quick plan: `draht-tools create-quick-plan NNN "$ARGUMENTS"`
18
+ 2. Analyze the task and write a concrete plan with actual task details (files, actions, verification). Pipe it into `draht-tools create-quick-plan`:
19
+ ```
20
+ echo 'plan content here' | draht-tools create-quick-plan NNN "$ARGUMENTS"
21
+ ```
22
+ The plan content must include: a `# Quick Task NNN: title` heading, a `## Tasks` section with one or more `<task>` XML blocks containing real file paths, real actions, and real verification steps — NOT placeholders like `[files]`.
19
23
  3. **Delegate execution to subagent**: Use the `subagent` tool in **single mode** with the `implementer` agent:
20
24
  "Execute this task: $ARGUMENTS
21
25