chief-clancy 0.5.12 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +15 -8
  2. package/dist/bundle/clancy-afk.js +6 -2
  3. package/dist/bundle/clancy-once.js +71 -48
  4. package/dist/installer/hook-installer/hook-installer.d.ts +2 -0
  5. package/dist/installer/hook-installer/hook-installer.d.ts.map +1 -1
  6. package/dist/installer/hook-installer/hook-installer.js +36 -1
  7. package/dist/installer/hook-installer/hook-installer.js.map +1 -1
  8. package/dist/installer/install.js +16 -1
  9. package/dist/installer/install.js.map +1 -1
  10. package/dist/schemas/env.d.ts +36 -0
  11. package/dist/schemas/env.d.ts.map +1 -1
  12. package/dist/schemas/env.js +11 -0
  13. package/dist/schemas/env.js.map +1 -1
  14. package/dist/schemas/github-issues.d.ts +6 -0
  15. package/dist/schemas/github-issues.d.ts.map +1 -1
  16. package/dist/schemas/github-issues.js +3 -0
  17. package/dist/schemas/github-issues.js.map +1 -1
  18. package/dist/schemas/jira.d.ts +21 -0
  19. package/dist/schemas/jira.d.ts.map +1 -1
  20. package/dist/schemas/jira.js +18 -0
  21. package/dist/schemas/jira.js.map +1 -1
  22. package/dist/schemas/linear.d.ts +41 -0
  23. package/dist/schemas/linear.d.ts.map +1 -1
  24. package/dist/schemas/linear.js +34 -0
  25. package/dist/schemas/linear.js.map +1 -1
  26. package/dist/scripts/afk/afk.d.ts.map +1 -1
  27. package/dist/scripts/afk/afk.js +28 -0
  28. package/dist/scripts/afk/afk.js.map +1 -1
  29. package/dist/scripts/afk/report/report.d.ts +47 -0
  30. package/dist/scripts/afk/report/report.d.ts.map +1 -0
  31. package/dist/scripts/afk/report/report.js +194 -0
  32. package/dist/scripts/afk/report/report.js.map +1 -0
  33. package/dist/scripts/board/github/github.d.ts +33 -3
  34. package/dist/scripts/board/github/github.d.ts.map +1 -1
  35. package/dist/scripts/board/github/github.js +112 -27
  36. package/dist/scripts/board/github/github.js.map +1 -1
  37. package/dist/scripts/board/jira/jira.d.ts +36 -4
  38. package/dist/scripts/board/jira/jira.d.ts.map +1 -1
  39. package/dist/scripts/board/jira/jira.js +138 -65
  40. package/dist/scripts/board/jira/jira.js.map +1 -1
  41. package/dist/scripts/board/linear/linear.d.ts +32 -5
  42. package/dist/scripts/board/linear/linear.d.ts.map +1 -1
  43. package/dist/scripts/board/linear/linear.js +135 -17
  44. package/dist/scripts/board/linear/linear.js.map +1 -1
  45. package/dist/scripts/once/board-ops/board-ops.d.ts.map +1 -1
  46. package/dist/scripts/once/board-ops/board-ops.js +1 -1
  47. package/dist/scripts/once/board-ops/board-ops.js.map +1 -1
  48. package/dist/scripts/once/cost/cost.d.ts +10 -0
  49. package/dist/scripts/once/cost/cost.d.ts.map +1 -0
  50. package/dist/scripts/once/cost/cost.js +23 -0
  51. package/dist/scripts/once/cost/cost.js.map +1 -0
  52. package/dist/scripts/once/deliver/deliver.d.ts.map +1 -1
  53. package/dist/scripts/once/deliver/deliver.js +18 -1
  54. package/dist/scripts/once/deliver/deliver.js.map +1 -1
  55. package/dist/scripts/once/fetch-ticket/fetch-ticket.d.ts +19 -1
  56. package/dist/scripts/once/fetch-ticket/fetch-ticket.d.ts.map +1 -1
  57. package/dist/scripts/once/fetch-ticket/fetch-ticket.js +93 -29
  58. package/dist/scripts/once/fetch-ticket/fetch-ticket.js.map +1 -1
  59. package/dist/scripts/once/lock/lock.d.ts +17 -0
  60. package/dist/scripts/once/lock/lock.d.ts.map +1 -0
  61. package/dist/scripts/once/lock/lock.js +70 -0
  62. package/dist/scripts/once/lock/lock.js.map +1 -0
  63. package/dist/scripts/once/once.d.ts.map +1 -1
  64. package/dist/scripts/once/once.js +100 -4
  65. package/dist/scripts/once/once.js.map +1 -1
  66. package/dist/scripts/once/resume/resume.d.ts +24 -0
  67. package/dist/scripts/once/resume/resume.d.ts.map +1 -0
  68. package/dist/scripts/once/resume/resume.js +159 -0
  69. package/dist/scripts/once/resume/resume.js.map +1 -0
  70. package/dist/scripts/shared/format/format.d.ts.map +1 -1
  71. package/dist/scripts/shared/format/format.js +6 -1
  72. package/dist/scripts/shared/format/format.js.map +1 -1
  73. package/dist/scripts/shared/progress/progress.d.ts +18 -2
  74. package/dist/scripts/shared/progress/progress.d.ts.map +1 -1
  75. package/dist/scripts/shared/progress/progress.js +31 -4
  76. package/dist/scripts/shared/progress/progress.js.map +1 -1
  77. package/dist/scripts/shared/pull-request/pr-body/pr-body.d.ts +2 -1
  78. package/dist/scripts/shared/pull-request/pr-body/pr-body.d.ts.map +1 -1
  79. package/dist/scripts/shared/pull-request/pr-body/pr-body.js +10 -1
  80. package/dist/scripts/shared/pull-request/pr-body/pr-body.js.map +1 -1
  81. package/dist/types/remote.d.ts +1 -1
  82. package/dist/types/remote.d.ts.map +1 -1
  83. package/hooks/clancy-branch-guard.js +129 -0
  84. package/hooks/clancy-check-update.js +43 -0
  85. package/hooks/clancy-context-monitor.js +134 -46
  86. package/hooks/clancy-post-compact.js +53 -0
  87. package/hooks/package.json +3 -0
  88. package/package.json +3 -2
  89. package/src/agents/devils-advocate.md +53 -0
  90. package/src/agents/verification-gate.md +128 -0
  91. package/src/roles/planner/workflows/approve-plan.md +2 -2
  92. package/src/roles/reviewer/workflows/logs.md +9 -6
  93. package/src/roles/setup/commands/help.md +7 -0
  94. package/src/roles/setup/workflows/init.md +111 -6
  95. package/src/roles/setup/workflows/scaffold.md +57 -0
  96. package/src/roles/setup/workflows/settings.md +145 -0
  97. package/src/roles/setup/workflows/update.md +18 -0
  98. package/src/roles/strategist/commands/approve-brief.md +20 -0
  99. package/src/roles/strategist/commands/brief.md +27 -0
  100. package/src/roles/strategist/workflows/approve-brief.md +763 -0
  101. package/src/roles/strategist/workflows/brief.md +732 -0
  102. package/src/templates/CLAUDE.md +8 -1
@@ -17,9 +17,10 @@ export function isEpicBranch(targetBranch) {
17
17
  * @param config - The board configuration.
18
18
  * @param ticket - The ticket being implemented.
19
19
  * @param targetBranch - The branch the PR targets (used to determine `Closes` vs `Part of`).
20
+ * @param verificationWarning - Optional warning text when verification checks failed after max retries. Included as a `## Verification Warning` section before the footer.
20
21
  * @returns The PR body as a markdown string.
21
22
  */
22
- export function buildPrBody(config, ticket, targetBranch) {
23
+ export function buildPrBody(config, ticket, targetBranch, verificationWarning) {
23
24
  const lines = [];
24
25
  const isEpic = targetBranch ? isEpicBranch(targetBranch) : false;
25
26
  switch (config.provider) {
@@ -40,6 +41,14 @@ export function buildPrBody(config, ticket, targetBranch) {
40
41
  lines.push(ticket.description);
41
42
  lines.push('');
42
43
  }
44
+ if (verificationWarning) {
45
+ lines.push('## ⚠ Verification Warning');
46
+ lines.push('');
47
+ lines.push(verificationWarning);
48
+ lines.push('');
49
+ lines.push('This PR may need manual fixes before merging.');
50
+ lines.push('');
51
+ }
43
52
  lines.push('---');
44
53
  lines.push('*Created by [Clancy](https://github.com/Pushedskydiver/clancy)*');
45
54
  lines.push('');
@@ -1 +1 @@
1
- {"version":3,"file":"pr-body.js","sourceRoot":"","sources":["../../../../../src/scripts/shared/pull-request/pr-body/pr-body.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pr-body.js","sourceRoot":"","sources":["../../../../../src/scripts/shared/pull-request/pr-body/pr-body.ts"],"names":[],"mappings":"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"}
@@ -51,5 +51,5 @@ export type PrReviewState = {
51
51
  reviewers?: string[];
52
52
  };
53
53
  /** Progress log status values. */
54
- export type ProgressStatus = 'DONE' | 'SKIPPED' | 'PR_CREATED' | 'PUSHED' | 'PUSH_FAILED' | 'LOCAL' | 'PLAN' | 'APPROVE' | 'REWORK' | 'EPIC_PR_CREATED';
54
+ export type ProgressStatus = 'DONE' | 'SKIPPED' | 'PR_CREATED' | 'PUSHED' | 'PUSH_FAILED' | 'LOCAL' | 'PLAN' | 'APPROVE_PLAN' | 'REWORK' | 'EPIC_PR_CREATED' | 'BRIEF' | 'APPROVE_BRIEF' | 'TIME_LIMIT' | 'RESUMED';
55
55
  //# sourceMappingURL=remote.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../src/types/remote.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,uCAAuC;AACvC,MAAM,MAAM,WAAW,GACnB,QAAQ,GACR,QAAQ,GACR,WAAW,GACX,kBAAkB,GAClB,OAAO,GACP,SAAS,CAAC;AAEd,qDAAqD;AACrD,MAAM,MAAM,UAAU,GAClB;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,kBAAkB,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb,GACD;IACE,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEN,0CAA0C;AAC1C,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1D,6CAA6C;AAC7C,MAAM,MAAM,aAAa,GAAG;IAC1B,yDAAyD;IACzD,gBAAgB,EAAE,OAAO,CAAC;IAC1B,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,kCAAkC;AAClC,MAAM,MAAM,cAAc,GACtB,MAAM,GACN,SAAS,GACT,YAAY,GACZ,QAAQ,GACR,aAAa,GACb,OAAO,GACP,MAAM,GACN,SAAS,GACT,QAAQ,GACR,iBAAiB,CAAC"}
1
+ {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../src/types/remote.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,uCAAuC;AACvC,MAAM,MAAM,WAAW,GACnB,QAAQ,GACR,QAAQ,GACR,WAAW,GACX,kBAAkB,GAClB,OAAO,GACP,SAAS,CAAC;AAEd,qDAAqD;AACrD,MAAM,MAAM,UAAU,GAClB;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,kBAAkB,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb,GACD;IACE,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEN,0CAA0C;AAC1C,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1D,6CAA6C;AAC7C,MAAM,MAAM,aAAa,GAAG;IAC1B,yDAAyD;IACzD,gBAAgB,EAAE,OAAO,CAAC;IAC1B,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,kCAAkC;AAClC,MAAM,MAAM,cAAc,GACtB,MAAM,GACN,SAAS,GACT,YAAY,GACZ,QAAQ,GACR,aAAa,GACb,OAAO,GACP,MAAM,GACN,cAAc,GACd,QAAQ,GACR,iBAAiB,GACjB,OAAO,GACP,eAAe,GACf,YAAY,GACZ,SAAS,CAAC"}
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ // Clancy Branch Guard — PreToolUse hook.
3
+ // Blocks dangerous git operations: force push, push to protected branches,
4
+ // hard reset, clean, bulk discard, and force branch deletion.
5
+ // Best-effort — never blocks on error (fail-open).
6
+
7
+ 'use strict';
8
+
9
+ // Protected branch names — pushes to these are blocked.
10
+ // CLANCY_BASE_BRANCH is added dynamically if set and not already in the list.
11
+ const PROTECTED_BRANCHES = ['main', 'master', 'develop'];
12
+ if (process.env.CLANCY_BASE_BRANCH && !PROTECTED_BRANCHES.includes(process.env.CLANCY_BASE_BRANCH)) {
13
+ PROTECTED_BRANCHES.push(process.env.CLANCY_BASE_BRANCH);
14
+ }
15
+
16
+ /**
17
+ * Check whether a command string contains a dangerous git operation.
18
+ * Returns a reason string if blocked, or null if allowed.
19
+ */
20
+ function checkCommand(cmd) {
21
+ if (!cmd || typeof cmd !== 'string') return null;
22
+
23
+ // --- git push --force / -f (without --force-with-lease) ---
24
+ // Match any occurrence of "git push" with --force or -f flag
25
+ if (/\bgit\s+push\b/.test(cmd)) {
26
+ const hasForceFlag = /\s--force\b/.test(cmd) || /\s-f\b/.test(cmd);
27
+ const hasForceWithLease = /--force-with-lease\b/.test(cmd);
28
+ if (hasForceFlag && !hasForceWithLease) {
29
+ return 'Blocked: git push --force destroys remote history. Use --force-with-lease instead.';
30
+ }
31
+
32
+ // --- git push to protected branches ---
33
+ // Pattern: git push <remote> <protected-branch>
34
+ // We look for "git push" followed by any remote name, then a protected branch.
35
+ // The branch must be a standalone token — \b treats hyphens as word boundaries,
36
+ // so we match the branch as a complete whitespace-delimited token instead.
37
+ for (const branch of PROTECTED_BRANCHES) {
38
+ // Matches: git push origin main, git push origin main:main, etc.
39
+ // Does NOT match: git push origin main-feature
40
+ // Escape regex metacharacters in branch name (e.g. release/1.0 → release\/1\.0)
41
+ const escaped = branch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
42
+ const pattern = new RegExp(`\\bgit\\s+push\\s+\\S+\\s+${escaped}(?:\\s|$|:)`);
43
+ if (pattern.test(cmd)) {
44
+ return `Blocked: direct push to protected branch '${branch}'. Create a PR instead.`;
45
+ }
46
+ }
47
+ }
48
+
49
+ // --- git reset --hard ---
50
+ if (/\bgit\s+reset\s+--hard\b/.test(cmd)) {
51
+ return 'Blocked: git reset --hard destroys uncommitted work. Use --soft or --mixed instead.';
52
+ }
53
+
54
+ // --- git clean -f (any variant with -f but not just -n) ---
55
+ // Block git clean with -f flag (e.g. -f, -fd, -fdx, -xfd, etc.) but allow -n (dry run)
56
+ if (/\bgit\s+clean\b/.test(cmd)) {
57
+ // Check for -f flag in the clean arguments
58
+ // Match short flags like -f, -fd, -fdx, -xf, etc.
59
+ const cleanMatch = cmd.match(/\bgit\s+clean\s+(.*)/);
60
+ if (cleanMatch) {
61
+ const args = cleanMatch[1];
62
+ // Check for -f in combined short flags or standalone
63
+ if (/(?:^|\s)-[a-zA-Z]*f/.test(args) && !/(?:^|\s)-n\b/.test(args)) {
64
+ return 'Blocked: git clean -f deletes untracked files permanently. Use -n for a dry run first.';
65
+ }
66
+ }
67
+ }
68
+
69
+ // --- git checkout -- . (discard all changes) ---
70
+ if (/\bgit\s+checkout\s+--\s+\./.test(cmd)) {
71
+ return 'Blocked: git checkout -- . discards all uncommitted changes.';
72
+ }
73
+
74
+ // --- git restore . (discard all changes) ---
75
+ // Only block bare "git restore ." (all files), not "git restore ./specific-file"
76
+ if (/\bgit\s+restore\s+\.(?:\s*$|\s*[;&|])/.test(cmd)) {
77
+ return 'Blocked: git restore . discards all uncommitted changes.';
78
+ }
79
+
80
+ // --- git branch -D (force delete) ---
81
+ // Block uppercase -D only, not lowercase -d
82
+ if (/\bgit\s+branch\s+.*-D\b/.test(cmd)) {
83
+ return 'Blocked: git branch -D force-deletes a branch irrecoverably. Use -d for safe deletion.';
84
+ }
85
+
86
+ return null;
87
+ }
88
+
89
+ // Read hook input — Claude Code passes PreToolUse data as a JSON argument.
90
+ // Fall back to stdin for forward compatibility with potential API changes.
91
+ function readInput() {
92
+ if (process.argv[2]) return process.argv[2];
93
+ try { return require('fs').readFileSync('/dev/stdin', 'utf8'); } catch { return '{}'; }
94
+ }
95
+
96
+ try {
97
+ // Check if guard is disabled
98
+ if (process.env.CLANCY_BRANCH_GUARD === 'false') {
99
+ console.log(JSON.stringify({ decision: 'approve' }));
100
+ process.exit(0);
101
+ }
102
+
103
+ const input = JSON.parse(readInput());
104
+ const toolName = input.tool_name || '';
105
+ const toolInput = input.tool_input || {};
106
+
107
+ // Only check Bash tool calls
108
+ if (toolName !== 'Bash') {
109
+ console.log(JSON.stringify({ decision: 'approve' }));
110
+ process.exit(0);
111
+ }
112
+
113
+ const cmd = (toolInput.command || '').trim();
114
+ const reason = checkCommand(cmd);
115
+
116
+ if (reason) {
117
+ console.log(JSON.stringify({ decision: 'block', reason }));
118
+ } else {
119
+ console.log(JSON.stringify({ decision: 'approve' }));
120
+ }
121
+ } catch {
122
+ // Best-effort — never block on error
123
+ console.log(JSON.stringify({ decision: 'approve' }));
124
+ }
125
+
126
+ // Export for testing
127
+ if (typeof module !== 'undefined') {
128
+ module.exports = { checkCommand };
129
+ }
@@ -69,3 +69,46 @@ const child = spawn(process.execPath, ['-e', `
69
69
  });
70
70
 
71
71
  child.unref();
72
+
73
+ // --- Stale brief detection ---
74
+ // Best-effort: detect unapproved briefs older than 7 days, write count to cache file.
75
+ try {
76
+ const briefsDir = path.join(cwd, '.clancy', 'briefs');
77
+ const staleFile = path.join(cwd, '.clancy', '.brief-stale-count');
78
+
79
+ if (!fs.existsSync(briefsDir)) {
80
+ // No briefs dir — clear any stale cache so old counts don't linger
81
+ try { fs.unlinkSync(staleFile); } catch { /* may not exist */ }
82
+ } else {
83
+ const files = fs.readdirSync(briefsDir);
84
+ // Only count brief files (YYYY-MM-DD-slug.md), exclude .feedback.md and .approved markers
85
+ const mdFiles = files.filter(f =>
86
+ f.endsWith('.md') &&
87
+ !f.endsWith('.md.approved') &&
88
+ !f.endsWith('.feedback.md')
89
+ );
90
+ const now = Date.now();
91
+ const sevenDays = 7 * 24 * 60 * 60 * 1000;
92
+ let staleCount = 0;
93
+
94
+ for (const file of mdFiles) {
95
+ // Check there is no corresponding .approved marker
96
+ if (files.includes(file + '.approved')) continue;
97
+
98
+ // Parse date from filename prefix: YYYY-MM-DD-slug.md
99
+ const match = file.match(/^(\d{4}-\d{2}-\d{2})-/);
100
+ if (!match) continue;
101
+
102
+ const fileDate = new Date(match[1] + 'T00:00:00Z');
103
+ if (isNaN(fileDate.getTime())) continue;
104
+
105
+ if (now - fileDate.getTime() > sevenDays) {
106
+ staleCount++;
107
+ }
108
+ }
109
+
110
+ fs.writeFileSync(staleFile, String(staleCount));
111
+ }
112
+ } catch {
113
+ // Best-effort — never crash the hook
114
+ }
@@ -3,11 +3,17 @@
3
3
  // Reads context metrics from the bridge file written by clancy-statusline.js
4
4
  // and injects warnings into Claude's conversation when context runs low.
5
5
  //
6
- // Thresholds:
6
+ // Context thresholds:
7
7
  // WARNING (remaining <= 35%): wrap up analysis, move to implementation
8
8
  // CRITICAL (remaining <= 25%): commit current work, log to .clancy/progress.txt, stop
9
9
  //
10
+ // Time guard:
11
+ // Reads .clancy/lock.json for startedAt timestamp.
12
+ // WARNING (elapsed >= 80% of CLANCY_TIME_LIMIT): wrap up implementation
13
+ // CRITICAL (elapsed >= 100% of CLANCY_TIME_LIMIT): stop and deliver
14
+ //
10
15
  // Debounce: 5 tool uses between warnings; severity escalation bypasses debounce.
16
+ // Context and time guards use independent debounce counters.
11
17
 
12
18
  'use strict';
13
19
 
@@ -19,6 +25,7 @@ const WARNING_THRESHOLD = 35;
19
25
  const CRITICAL_THRESHOLD = 25;
20
26
  const STALE_SECONDS = 60;
21
27
  const DEBOUNCE_CALLS = 5;
28
+ const DEFAULT_TIME_LIMIT = 30; // minutes
22
29
 
23
30
  let input = '';
24
31
  const stdinTimeout = setTimeout(() => process.exit(0), 3000);
@@ -31,66 +38,147 @@ process.stdin.on('end', () => {
31
38
  const session = data.session_id;
32
39
  if (!session) process.exit(0);
33
40
 
34
- const bridgePath = path.join(os.tmpdir(), `clancy-ctx-${session}.json`);
35
- if (!fs.existsSync(bridgePath)) process.exit(0); // no statusline data yet
36
-
37
- const metrics = JSON.parse(fs.readFileSync(bridgePath, 'utf8'));
38
- const now = Math.floor(Date.now() / 1000);
39
-
40
- // Ignore stale metrics (statusline may not have run this session)
41
- if (metrics.timestamp && (now - metrics.timestamp) > STALE_SECONDS) process.exit(0);
42
-
43
- const remaining = metrics.remaining_percentage;
44
- const usedPct = metrics.used_pct;
45
-
46
- if (remaining > WARNING_THRESHOLD) process.exit(0);
41
+ const messages = [];
47
42
 
48
- // Debounce
43
+ // ── Warn file (shared by context + time debounce) ───────────
49
44
  const warnPath = path.join(os.tmpdir(), `clancy-ctx-${session}-warned.json`);
50
- let warnData = { callsSinceWarn: 0, lastLevel: null };
51
- let firstWarn = true;
45
+ let warnData = {
46
+ callsSinceWarn: 0,
47
+ lastLevel: null,
48
+ timeCallsSinceWarn: 0,
49
+ timeLastLevel: null,
50
+ };
51
+ let firstContextWarn = true;
52
+ let firstTimeWarn = true;
52
53
 
53
54
  if (fs.existsSync(warnPath)) {
54
- try { warnData = JSON.parse(fs.readFileSync(warnPath, 'utf8')); firstWarn = false; } catch {}
55
+ try {
56
+ const existing = JSON.parse(fs.readFileSync(warnPath, 'utf8'));
57
+ warnData = { ...warnData, ...existing };
58
+ firstContextWarn = !existing.lastLevel;
59
+ firstTimeWarn = !existing.timeLastLevel;
60
+ } catch {}
55
61
  }
56
62
 
57
- warnData.callsSinceWarn = (warnData.callsSinceWarn || 0) + 1;
63
+ // ── Context guard ───────────────────────────────────────────
64
+ const bridgePath = path.join(os.tmpdir(), `clancy-ctx-${session}.json`);
65
+ let contextFired = false;
66
+
67
+ if (fs.existsSync(bridgePath)) {
68
+ const metrics = JSON.parse(fs.readFileSync(bridgePath, 'utf8'));
69
+ const now = Math.floor(Date.now() / 1000);
70
+
71
+ const isStale = metrics.timestamp && (now - metrics.timestamp) > STALE_SECONDS;
72
+ if (!isStale) {
73
+ const remaining = metrics.remaining_percentage;
74
+ const usedPct = metrics.used_pct;
75
+
76
+ if (remaining <= WARNING_THRESHOLD) {
77
+ warnData.callsSinceWarn = (warnData.callsSinceWarn || 0) + 1;
78
+
79
+ const isCritical = remaining <= CRITICAL_THRESHOLD;
80
+ const currentLevel = isCritical ? 'critical' : 'warning';
81
+ const severityEscalated = currentLevel === 'critical' && warnData.lastLevel === 'warning';
82
+
83
+ const shouldFire = firstContextWarn ||
84
+ warnData.callsSinceWarn >= DEBOUNCE_CALLS ||
85
+ severityEscalated;
86
+
87
+ if (shouldFire) {
88
+ warnData.callsSinceWarn = 0;
89
+ warnData.lastLevel = currentLevel;
90
+ contextFired = true;
91
+
92
+ if (isCritical) {
93
+ messages.push(
94
+ `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
95
+ 'Context is nearly exhausted. Stop reading files and wrap up immediately:\n' +
96
+ '1. Commit whatever work is staged on the current feature branch\n' +
97
+ '2. Append a WIP entry to .clancy/progress.txt: ' +
98
+ 'YYYY-MM-DD HH:MM | TICKET-KEY | Summary | WIP — context exhausted\n' +
99
+ '3. Inform the user what was completed and what remains.\n' +
100
+ 'Do NOT start any new work.'
101
+ );
102
+ } else {
103
+ messages.push(
104
+ `CONTEXT WARNING: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
105
+ 'Context is getting limited. Stop exploring and move to implementation. ' +
106
+ 'Avoid reading additional files unless strictly necessary. ' +
107
+ 'Commit completed work as soon as it is ready.'
108
+ );
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
58
114
 
59
- const isCritical = remaining <= CRITICAL_THRESHOLD;
60
- const currentLevel = isCritical ? 'critical' : 'warning';
61
- const severityEscalated = currentLevel === 'critical' && warnData.lastLevel === 'warning';
115
+ // ── Time guard ──────────────────────────────────────────────
116
+ const timeLimitEnv = process.env.CLANCY_TIME_LIMIT;
117
+ const timeLimit = timeLimitEnv !== undefined ? Number(timeLimitEnv) : DEFAULT_TIME_LIMIT;
118
+
119
+ if (timeLimit > 0) {
120
+ // Look for lock.json in the working directory's .clancy/
121
+ const cwd = data.cwd || process.cwd();
122
+ const lockPath = path.join(cwd, '.clancy', 'lock.json');
123
+
124
+ if (fs.existsSync(lockPath)) {
125
+ try {
126
+ const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
127
+ const startedAt = new Date(lock.startedAt);
128
+
129
+ if (!isNaN(startedAt.getTime())) {
130
+ const elapsedMs = Date.now() - startedAt.getTime();
131
+ const elapsedMin = Math.floor(elapsedMs / 60000);
132
+ const limitMs = timeLimit * 60000;
133
+ const pct = Math.floor((elapsedMs / limitMs) * 100);
134
+
135
+ if (pct >= 80) {
136
+ warnData.timeCallsSinceWarn = (warnData.timeCallsSinceWarn || 0) + 1;
137
+
138
+ const isTimeCritical = pct >= 100;
139
+ const currentTimeLevel = isTimeCritical ? 'critical' : 'warning';
140
+ const timeSeverityEscalated =
141
+ currentTimeLevel === 'critical' && warnData.timeLastLevel === 'warning';
142
+
143
+ const shouldFireTime = firstTimeWarn ||
144
+ warnData.timeCallsSinceWarn >= DEBOUNCE_CALLS ||
145
+ timeSeverityEscalated;
146
+
147
+ if (shouldFireTime) {
148
+ warnData.timeCallsSinceWarn = 0;
149
+ warnData.timeLastLevel = currentTimeLevel;
150
+
151
+ if (isTimeCritical) {
152
+ messages.push(
153
+ `TIME CRITICAL: Time limit reached (${elapsedMin}min of ${timeLimit}min).\n` +
154
+ 'STOP implementation immediately. Commit current work, push the branch,\n' +
155
+ 'and create the PR with whatever is ready. Log a WIP entry if incomplete.'
156
+ );
157
+ } else {
158
+ messages.push(
159
+ `TIME WARNING: Ticket implementation at ${elapsedMin}min of ${timeLimit}min limit (${pct}%).\n` +
160
+ 'Wrap up implementation and prepare for delivery. Avoid starting new approaches.'
161
+ );
162
+ }
163
+ }
164
+ }
165
+ }
166
+ } catch {}
167
+ }
168
+ }
62
169
 
63
- if (!firstWarn && warnData.callsSinceWarn < DEBOUNCE_CALLS && !severityEscalated) {
170
+ // ── Persist debounce state (only when a threshold was reached) ──
171
+ if (messages.length > 0 || contextFired || warnData.callsSinceWarn > 0 || warnData.timeCallsSinceWarn > 0) {
64
172
  fs.writeFileSync(warnPath, JSON.stringify(warnData));
65
- process.exit(0);
66
173
  }
67
174
 
68
- warnData.callsSinceWarn = 0;
69
- warnData.lastLevel = currentLevel;
70
- fs.writeFileSync(warnPath, JSON.stringify(warnData));
71
-
72
- let message;
73
- if (isCritical) {
74
- message =
75
- `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
76
- 'Context is nearly exhausted. Stop reading files and wrap up immediately:\n' +
77
- '1. Commit whatever work is staged on the current feature branch\n' +
78
- '2. Append a WIP entry to .clancy/progress.txt: ' +
79
- 'YYYY-MM-DD HH:MM | TICKET-KEY | Summary | WIP — context exhausted\n' +
80
- '3. Inform the user what was completed and what remains.\n' +
81
- 'Do NOT start any new work.';
82
- } else {
83
- message =
84
- `CONTEXT WARNING: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
85
- 'Context is getting limited. Stop exploring and move to implementation. ' +
86
- 'Avoid reading additional files unless strictly necessary. ' +
87
- 'Commit completed work as soon as it is ready.';
88
- }
175
+ // ── Output ──────────────────────────────────────────────────
176
+ if (messages.length === 0) process.exit(0);
89
177
 
90
178
  const output = {
91
179
  hookSpecificOutput: {
92
180
  hookEventName: 'PostToolUse',
93
- additionalContext: message,
181
+ additionalContext: messages.join('\n'),
94
182
  },
95
183
  };
96
184
 
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ // Clancy PostCompact Hook — re-injects ticket context after context compaction.
3
+ // Reads .clancy/lock.json for current ticket info. If no lock file exists
4
+ // (not in a Clancy run), exits silently.
5
+ //
6
+ // PostCompact is a non-blocking event — this hook injects additionalContext
7
+ // but cannot prevent compaction.
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ let input = '';
15
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
16
+ process.stdin.setEncoding('utf8');
17
+ process.stdin.on('data', chunk => { input += chunk; });
18
+ process.stdin.on('end', () => {
19
+ clearTimeout(stdinTimeout);
20
+ try {
21
+ const data = JSON.parse(input);
22
+ const cwd = data.cwd || process.cwd();
23
+
24
+ // Read lock file for ticket context
25
+ const lockPath = path.join(cwd, '.clancy', 'lock.json');
26
+ if (!fs.existsSync(lockPath)) process.exit(0); // Not in a Clancy run
27
+
28
+ const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
29
+
30
+ // Validate minimum required fields — skip if lock is malformed
31
+ if (!lock.ticketKey || !lock.ticketBranch) process.exit(0);
32
+
33
+ // Truncate description to 2000 chars to avoid consuming too much fresh context
34
+ const desc = (lock.description || '').slice(0, 2000);
35
+
36
+ const context = [
37
+ `CONTEXT RESTORED: You are implementing ticket [${lock.ticketKey}] ${lock.ticketTitle || 'Unknown'}.`,
38
+ `Branch: ${lock.ticketBranch} targeting ${lock.targetBranch || 'main'}.`,
39
+ lock.parentKey && lock.parentKey !== 'none' ? `Parent: ${lock.parentKey}.` : '',
40
+ desc ? `Requirements: ${desc}` : '',
41
+ 'Continue your implementation. Do not start over.',
42
+ ].filter(Boolean).join('\n');
43
+
44
+ process.stdout.write(JSON.stringify({
45
+ hookSpecificOutput: {
46
+ hookEventName: 'PostCompact',
47
+ additionalContext: context,
48
+ },
49
+ }));
50
+ } catch {
51
+ process.exit(0); // Best-effort — never crash
52
+ }
53
+ });
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chief-clancy",
3
- "version": "0.5.12",
3
+ "version": "0.7.0",
4
4
  "description": "Autonomous, board-driven development for Claude Code — scaffolds docs, integrates Kanban boards, runs tickets in a loop.",
5
5
  "keywords": [
6
6
  "claude",
@@ -30,7 +30,8 @@
30
30
  "main": "dist/installer/install.js",
31
31
  "files": [
32
32
  "dist/",
33
- "hooks/",
33
+ "hooks/*.js",
34
+ "hooks/package.json",
34
35
  "src/roles/",
35
36
  "src/agents/",
36
37
  "src/templates/",
@@ -0,0 +1,53 @@
1
+ # Devil's Advocate Agent
2
+
3
+ You are the devil's advocate agent for Clancy's strategist role. Your job is to answer a list of clarifying questions about a feature idea by interrogating the codebase, board, and web — then classify each answer by confidence.
4
+
5
+ You receive 10-15 clarifying questions generated during the AI-grill phase of `/clancy:brief --afk`. You must answer them autonomously. Never ask the human for input — this runs in AFK mode with no human present.
6
+
7
+ ## Instructions
8
+
9
+ 1. Work through each question one at a time. For every question, investigate before answering — never guess.
10
+ 2. Interrogate three sources in order of preference:
11
+ - **Codebase**: use Glob, Grep, and Read to explore affected areas, check `.clancy/docs/`, read existing patterns. Use real file paths and code snippets as evidence.
12
+ - **Board**: check the parent ticket, related tickets, and existing children for context. Look for conflicting requirements.
13
+ - **Web**: when the question involves external technology, third-party integrations, or industry patterns, use WebSearch and WebFetch.
14
+ 3. Challenge your own answers. If the codebase says one thing but the ticket description says another, flag the conflict — do not silently pick one.
15
+ 4. Follow self-generated follow-ups within the same pass. If answering a question raises a new sub-question, chase it to its conclusion before moving on. Example: "Should this support SSO?" → check codebase → find SAML provider → "SAML exists but should the new feature use SAML or add OIDC?" → check web → resolve or flag.
16
+ 5. Be RELENTLESS. If the codebase doesn't clearly support a decision, don't manufacture confidence. Put it in Open Questions.
17
+ 6. If a question is partially answerable, answer the part you can and flag the remainder as open.
18
+
19
+ ## How to classify
20
+
21
+ - **Answerable** (>80% confidence, clear codebase precedent or unambiguous external evidence) → include in Discovery section with source tag.
22
+ - **Conflicting** (codebase says X, ticket says Y, or two sources disagree) → include in Open Questions with the conflict described.
23
+ - **Not answerable** (business decision, ambiguous requirements, money/legal/compliance, no evidence in any source) → include in Open Questions for PO review.
24
+
25
+ ## Output format
26
+
27
+ Return exactly two markdown sections:
28
+
29
+ ```markdown
30
+ ## Discovery
31
+
32
+ Q: [question]
33
+ A: [answer with evidence]. (Source: codebase|board|web)
34
+
35
+ Q: [question]
36
+ A: [answer]. (Source: codebase)
37
+
38
+ ## Open Questions
39
+ - [ ] [question that couldn't be resolved — with reason]
40
+ - [ ] [question with conflicting evidence — conflict described]
41
+ ```
42
+
43
+ Every answer in Discovery must cite its source: `(Source: codebase)`, `(Source: board)`, `(Source: web)`, or `(Source: codebase, web)` for combined evidence.
44
+
45
+ ---
46
+
47
+ ## Rules
48
+
49
+ - NEVER ask the human questions. You are running autonomously.
50
+ - Single pass — no multi-round conversation loop. But each question must be followed to its conclusion including any self-follow-ups.
51
+ - Every answer must include real file paths, code snippets, ticket references, or URLs as evidence. No hand-waving.
52
+ - When you find no evidence at all, say so explicitly and classify as Open Questions.
53
+ - Prefer specificity over breadth. One concrete file path beats three vague claims.