cleargate 0.2.1 → 0.4.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/dist/MANIFEST.json +58 -16
  3. package/dist/admin-api/index.cjs +88 -1
  4. package/dist/admin-api/index.cjs.map +1 -1
  5. package/dist/admin-api/index.d.cts +105 -1
  6. package/dist/admin-api/index.d.ts +105 -1
  7. package/dist/admin-api/index.js +77 -1
  8. package/dist/admin-api/index.js.map +1 -1
  9. package/dist/bootstrap-root-FGWDICDT.js +130 -0
  10. package/dist/bootstrap-root-FGWDICDT.js.map +1 -0
  11. package/dist/chunk-OM4FAEA7.js +184 -0
  12. package/dist/chunk-OM4FAEA7.js.map +1 -0
  13. package/dist/cli.cjs +8530 -3957
  14. package/dist/cli.cjs.map +1 -1
  15. package/dist/cli.js +5282 -1220
  16. package/dist/cli.js.map +1 -1
  17. package/dist/templates/cleargate-planning/.claude/agents/architect.md +72 -0
  18. package/dist/templates/cleargate-planning/.claude/agents/developer.md +45 -3
  19. package/dist/templates/cleargate-planning/.claude/agents/qa.md +7 -3
  20. package/dist/templates/cleargate-planning/.claude/agents/reporter.md +71 -89
  21. package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
  22. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
  23. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
  24. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
  25. package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
  26. package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
  27. package/dist/templates/cleargate-planning/.claude/settings.json +11 -0
  28. package/dist/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
  29. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
  30. package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
  31. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
  32. package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
  33. package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
  34. package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
  35. package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
  36. package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
  37. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
  38. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
  39. package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
  40. package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
  41. package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
  42. package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
  43. package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
  44. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
  45. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
  46. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
  47. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
  48. package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
  49. package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
  50. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
  51. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
  52. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
  53. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
  54. package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
  55. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
  56. package/dist/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
  57. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
  58. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
  59. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +19 -0
  60. package/dist/templates/cleargate-planning/CLAUDE.md +2 -0
  61. package/dist/templates/cleargate-planning/MANIFEST.json +58 -16
  62. package/dist/whoami-CX7CXJD5.js +76 -0
  63. package/dist/whoami-CX7CXJD5.js.map +1 -0
  64. package/package.json +6 -2
  65. package/templates/cleargate-planning/.claude/agents/architect.md +72 -0
  66. package/templates/cleargate-planning/.claude/agents/developer.md +45 -3
  67. package/templates/cleargate-planning/.claude/agents/qa.md +7 -3
  68. package/templates/cleargate-planning/.claude/agents/reporter.md +71 -89
  69. package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
  70. package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
  71. package/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
  72. package/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
  73. package/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
  74. package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
  75. package/templates/cleargate-planning/.claude/settings.json +11 -0
  76. package/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
  77. package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
  78. package/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
  79. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
  80. package/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
  81. package/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
  82. package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
  83. package/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
  84. package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
  85. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
  86. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
  87. package/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
  88. package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
  89. package/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
  90. package/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
  91. package/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
  92. package/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
  93. package/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
  94. package/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
  95. package/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
  96. package/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
  97. package/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
  98. package/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
  99. package/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
  100. package/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
  101. package/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
  102. package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
  103. package/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
  104. package/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
  105. package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
  106. package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
  107. package/templates/cleargate-planning/.cleargate/templates/story.md +19 -0
  108. package/templates/cleargate-planning/CLAUDE.md +2 -0
  109. package/templates/cleargate-planning/MANIFEST.json +58 -16
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env bash
2
+ # run_script.sh — Wrapper that captures stdout/stderr separately and prints a
3
+ # structured diagnostic block on non-zero exit.
4
+ # Usage: run_script.sh <script-name> [args...]
5
+ # Supported extensions: .mjs (runs via node), .sh (runs via bash)
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+
10
+ # ---------------------------------------------------------------------------
11
+ # Usage guard
12
+ # ---------------------------------------------------------------------------
13
+ if [[ $# -lt 1 ]]; then
14
+ echo "Usage: run_script.sh <script-name> [args...]" >&2
15
+ exit 2
16
+ fi
17
+
18
+ SCRIPT_NAME="$1"
19
+ shift
20
+ SCRIPT_ARGS=()
21
+ if [[ $# -gt 0 ]]; then
22
+ SCRIPT_ARGS=("$@")
23
+ fi
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Resolve path — script may be an absolute path or relative to SCRIPT_DIR
27
+ # ---------------------------------------------------------------------------
28
+ if [[ "$SCRIPT_NAME" == /* ]]; then
29
+ SCRIPT_PATH="$SCRIPT_NAME"
30
+ else
31
+ SCRIPT_PATH="${SCRIPT_DIR}/${SCRIPT_NAME}"
32
+ fi
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Extension routing
36
+ # ---------------------------------------------------------------------------
37
+ EXT="${SCRIPT_NAME##*.}"
38
+ case "$EXT" in
39
+ mjs) RUNNER="node" ;;
40
+ sh) RUNNER="bash" ;;
41
+ *)
42
+ echo "unsupported extension: .${EXT}" >&2
43
+ exit 2
44
+ ;;
45
+ esac
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Check script exists
49
+ # ---------------------------------------------------------------------------
50
+ if [[ ! -f "$SCRIPT_PATH" ]]; then
51
+ echo "run_script.sh: script not found: ${SCRIPT_PATH}" >&2
52
+ exit 2
53
+ fi
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Capture stdout + stderr to temp files
57
+ # ---------------------------------------------------------------------------
58
+ STDOUT_FILE="$(mktemp)"
59
+ STDERR_FILE="$(mktemp)"
60
+ trap 'rm -f "$STDOUT_FILE" "$STDERR_FILE"' EXIT
61
+
62
+ EXIT_CODE=0
63
+ if [[ ${#SCRIPT_ARGS[@]} -gt 0 ]]; then
64
+ "$RUNNER" "$SCRIPT_PATH" "${SCRIPT_ARGS[@]}" > "$STDOUT_FILE" 2> "$STDERR_FILE" || EXIT_CODE=$?
65
+ else
66
+ "$RUNNER" "$SCRIPT_PATH" > "$STDOUT_FILE" 2> "$STDERR_FILE" || EXIT_CODE=$?
67
+ fi
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # On success: pass through and exit 0
71
+ # ---------------------------------------------------------------------------
72
+ if [[ $EXIT_CODE -eq 0 ]]; then
73
+ cat "$STDOUT_FILE"
74
+ cat "$STDERR_FILE" >&2
75
+ exit 0
76
+ fi
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # On failure: pass stdout through, then print structured diagnostic to stderr
80
+ # ---------------------------------------------------------------------------
81
+ cat "$STDOUT_FILE"
82
+
83
+ # Root-cause heuristic (6-branch)
84
+ STDERR_CONTENT="$(cat "$STDERR_FILE")"
85
+ ROOT_CAUSE="unknown error"
86
+ SUGGESTED_FIX="check the script output above for details"
87
+
88
+ if echo "$STDERR_CONTENT" | grep -q "state\.json not found"; then
89
+ ROOT_CAUSE="state.json not found — sprint may not be initialized"
90
+ SUGGESTED_FIX="run: node .cleargate/scripts/init_sprint.mjs <sprint-id> --stories <ids>"
91
+ elif echo "$STDERR_CONTENT" | grep -qi "ENOENT"; then
92
+ ROOT_CAUSE="missing file (ENOENT)"
93
+ SUGGESTED_FIX="verify all required files exist at the expected paths"
94
+ elif echo "$STDERR_CONTENT" | grep -qi "EACCES"; then
95
+ ROOT_CAUSE="permission denied (EACCES)"
96
+ SUGGESTED_FIX="chmod 755 the target file or directory"
97
+ elif echo "$STDERR_CONTENT" | grep -qi "SyntaxError"; then
98
+ ROOT_CAUSE="JavaScript syntax error"
99
+ SUGGESTED_FIX="fix the syntax error in the script; run: node --check <file>"
100
+ elif echo "$STDERR_CONTENT" | grep -qi "Cannot find module"; then
101
+ ROOT_CAUSE="missing module (import resolution failure)"
102
+ SUGGESTED_FIX="run npm install in the relevant package directory"
103
+ elif echo "$STDERR_CONTENT" | grep -qi "command not found"; then
104
+ ROOT_CAUSE="required command not found on PATH"
105
+ SUGGESTED_FIX="install the missing tool or add it to PATH"
106
+ fi
107
+
108
+ {
109
+ echo ""
110
+ echo "## Script Incident"
111
+ echo "Script: ${SCRIPT_NAME}"
112
+ echo "Runner: ${RUNNER}"
113
+ echo "Exit code: ${EXIT_CODE}"
114
+ echo ""
115
+ echo "### First 10 lines of stderr:"
116
+ echo "$STDERR_CONTENT" | head -10
117
+ echo ""
118
+ echo "Root cause: ${ROOT_CAUSE}"
119
+ echo "Suggested fix: ${SUGGESTED_FIX}"
120
+ echo "## End Incident"
121
+ } >&2
122
+
123
+ exit $EXIT_CODE
@@ -0,0 +1,110 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://cleargate.dev/schemas/state.v1.json",
4
+ "title": "ClearGate Sprint State v1",
5
+ "description": "Schema for .cleargate/sprint-runs/<sprint-id>/state.json. schema_version=1 is LOCKED — any field addition or removal must bump schema_version.",
6
+ "type": "object",
7
+ "required": [
8
+ "schema_version",
9
+ "sprint_id",
10
+ "execution_mode",
11
+ "sprint_status",
12
+ "stories",
13
+ "last_action",
14
+ "updated_at"
15
+ ],
16
+ "additionalProperties": false,
17
+ "properties": {
18
+ "schema_version": {
19
+ "type": "integer",
20
+ "const": 1,
21
+ "description": "Schema version. Must be 1 for v1 state files. Bump on any schema change."
22
+ },
23
+ "sprint_id": {
24
+ "type": "string",
25
+ "description": "Sprint identifier, e.g. S-09",
26
+ "examples": ["S-09", "S-10"]
27
+ },
28
+ "execution_mode": {
29
+ "type": "string",
30
+ "enum": ["v1", "v2"],
31
+ "description": "Execution mode for the sprint. v1 = legacy sequential; v2 = worktree-parallel."
32
+ },
33
+ "sprint_status": {
34
+ "type": "string",
35
+ "description": "Overall sprint status",
36
+ "examples": ["Active", "Closed"]
37
+ },
38
+ "stories": {
39
+ "type": "object",
40
+ "description": "Map of story ID to story state entry",
41
+ "additionalProperties": {
42
+ "$ref": "#/definitions/StoryEntry"
43
+ }
44
+ },
45
+ "last_action": {
46
+ "type": "string",
47
+ "description": "Human-readable description of the last operation performed"
48
+ },
49
+ "updated_at": {
50
+ "type": "string",
51
+ "format": "date-time",
52
+ "description": "ISO-8601 timestamp of last top-level update"
53
+ }
54
+ },
55
+ "definitions": {
56
+ "StoryEntry": {
57
+ "type": "object",
58
+ "required": [
59
+ "state",
60
+ "qa_bounces",
61
+ "arch_bounces",
62
+ "worktree",
63
+ "updated_at",
64
+ "notes"
65
+ ],
66
+ "additionalProperties": false,
67
+ "properties": {
68
+ "state": {
69
+ "type": "string",
70
+ "enum": [
71
+ "Ready to Bounce",
72
+ "Bouncing",
73
+ "QA Passed",
74
+ "Architect Passed",
75
+ "Sprint Review",
76
+ "Done",
77
+ "Escalated",
78
+ "Parking Lot"
79
+ ],
80
+ "description": "Current lifecycle state of the story"
81
+ },
82
+ "qa_bounces": {
83
+ "type": "integer",
84
+ "minimum": 0,
85
+ "maximum": 3,
86
+ "description": "Number of QA bounce cycles. Reaching 3 triggers auto-escalation."
87
+ },
88
+ "arch_bounces": {
89
+ "type": "integer",
90
+ "minimum": 0,
91
+ "maximum": 3,
92
+ "description": "Number of Architect bounce cycles. Reaching 3 triggers auto-escalation."
93
+ },
94
+ "worktree": {
95
+ "type": ["string", "null"],
96
+ "description": "Path to the story's git worktree checkout, or null if not in a worktree"
97
+ },
98
+ "updated_at": {
99
+ "type": "string",
100
+ "format": "date-time",
101
+ "description": "ISO-8601 timestamp of last update to this story entry"
102
+ },
103
+ "notes": {
104
+ "type": "string",
105
+ "description": "Free-form notes for this story (escalation reason, QA comments, etc.)"
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * suggest_improvements.mjs — Generate stable improvement suggestions from REPORT.md
4
+ *
5
+ * Usage: node suggest_improvements.mjs <sprint-id>
6
+ *
7
+ * Reads:
8
+ * - .cleargate/sprint-runs/<id>/REPORT.md §5 Framework Self-Assessment tables
9
+ * - .cleargate/sprint-runs/<prev-id>/improvement-suggestions.md (if present, for context)
10
+ *
11
+ * Emits:
12
+ * - .cleargate/sprint-runs/<id>/improvement-suggestions.md
13
+ * with stable SUG-<sprint>-<n> IDs
14
+ *
15
+ * Append-only idempotency (R5):
16
+ * - IDs are derived from a stable hash of (category, title) tuple
17
+ * - Re-running produces zero new entries if all suggestions already captured
18
+ * - Script exits 0 in both cases
19
+ *
20
+ * Note: "section" as used in §5 table extraction refers to the Framework Self-Assessment
21
+ * subsections: Templates, Handoffs, Skills, Process, Tooling.
22
+ */
23
+
24
+ import fs from 'node:fs';
25
+ import path from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
27
+ import { createHash } from 'node:crypto';
28
+
29
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
30
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
31
+
32
+ // §5 subsection names used in sprint_report.md template
33
+ const SELF_ASSESSMENT_SECTIONS = ['Templates', 'Handoffs', 'Skills', 'Process', 'Tooling'];
34
+
35
+ /**
36
+ * Generate a stable short hash from a string.
37
+ * Used for SUG ID stability — same (category, title) always produces same ID.
38
+ * @param {string} input
39
+ * @returns {string} 6-char hex
40
+ */
41
+ function stableHash(input) {
42
+ return createHash('sha256').update(input).digest('hex').slice(0, 6);
43
+ }
44
+
45
+ /**
46
+ * Parse §5 Framework Self-Assessment from REPORT.md content.
47
+ * Extracts Yellow/Red-rated rows as improvement candidates.
48
+ * @param {string} content
49
+ * @returns {{ category: string, item: string, rating: string, notes: string }[]}
50
+ */
51
+ function parseSelfAssessment(content) {
52
+ const suggestions = [];
53
+
54
+ // Find the §5 Framework Self-Assessment section
55
+ const selfAssessmentMatch = content.match(/##\s*§5[^\n]*\n([\s\S]*?)(?=##\s*§6|$)/);
56
+ if (!selfAssessmentMatch) return suggestions;
57
+
58
+ const sectionContent = selfAssessmentMatch[1];
59
+
60
+ // Extract each subsection table
61
+ for (const category of SELF_ASSESSMENT_SECTIONS) {
62
+ // Find table rows under the category header
63
+ // Pattern: ### <category>\n | <item> | <rating> | <notes> |
64
+ const categoryMatch = sectionContent.match(
65
+ new RegExp(`###\\s+${category}\\s*\\n([\\s\\S]*?)(?=###|$)`, 'i')
66
+ );
67
+ if (!categoryMatch) continue;
68
+
69
+ const tableContent = categoryMatch[1];
70
+ // Parse table rows (skip header rows with ---)
71
+ const rows = tableContent.split('\n').filter(l => l.startsWith('|') && !l.includes('---'));
72
+
73
+ for (const row of rows) {
74
+ const cells = row.split('|').map(c => c.trim()).filter(Boolean);
75
+ if (cells.length < 2) continue;
76
+
77
+ const item = cells[0];
78
+ const rating = cells[1] || '';
79
+ const notes = cells[2] || '';
80
+
81
+ // Only flag Yellow or Red items as needing improvement
82
+ if (rating.toLowerCase().includes('yellow') || rating.toLowerCase().includes('red')) {
83
+ // Skip header rows
84
+ if (item === 'Item' || item === '---') continue;
85
+ suggestions.push({ category, item, rating, notes });
86
+ }
87
+ }
88
+ }
89
+
90
+ return suggestions;
91
+ }
92
+
93
+ /**
94
+ * Parse existing improvement-suggestions.md to extract already-captured SUG IDs.
95
+ * @param {string} content
96
+ * @returns {Set<string>} Set of SUG IDs
97
+ */
98
+ function parseExistingIds(content) {
99
+ const ids = new Set();
100
+ // Match SUG-<sprint>-<n> patterns
101
+ const matches = content.matchAll(/SUG-[A-Z0-9-]+-\d+/g);
102
+ for (const m of matches) {
103
+ ids.add(m[0]);
104
+ }
105
+ return ids;
106
+ }
107
+
108
+ /**
109
+ * Atomic write using tmp+rename pattern.
110
+ * @param {string} filePath
111
+ * @param {string} content
112
+ */
113
+ function atomicWrite(filePath, content) {
114
+ const tmpFile = `${filePath}.tmp.${process.pid}`;
115
+ fs.writeFileSync(tmpFile, content, 'utf8');
116
+ fs.renameSync(tmpFile, filePath);
117
+ }
118
+
119
+ function main() {
120
+ const args = process.argv.slice(2);
121
+ if (args.length < 1) {
122
+ process.stderr.write('Usage: node suggest_improvements.mjs <sprint-id>\n');
123
+ process.exit(2);
124
+ }
125
+
126
+ const sprintId = args[0];
127
+ const sprintDir = process.env.CLEARGATE_SPRINT_DIR
128
+ ? path.resolve(process.env.CLEARGATE_SPRINT_DIR)
129
+ : path.join(REPO_ROOT, '.cleargate', 'sprint-runs', sprintId);
130
+
131
+ if (!fs.existsSync(sprintDir)) {
132
+ process.stderr.write(`Error: sprint directory not found: ${sprintDir}\n`);
133
+ process.exit(1);
134
+ }
135
+
136
+ const reportFile = path.join(sprintDir, 'REPORT.md');
137
+ const suggestionsFile = path.join(sprintDir, 'improvement-suggestions.md');
138
+
139
+ if (!fs.existsSync(reportFile)) {
140
+ process.stderr.write(`Error: REPORT.md not found at ${reportFile}\n`);
141
+ process.stderr.write('Run the Reporter agent first to generate the report.\n');
142
+ process.exit(1);
143
+ }
144
+
145
+ const reportContent = fs.readFileSync(reportFile, 'utf8');
146
+
147
+ // Parse §5 self-assessment for improvement candidates
148
+ const candidates = parseSelfAssessment(reportContent);
149
+
150
+ // Read existing suggestions if present (idempotency)
151
+ let existingContent = '';
152
+ const existingIds = new Set();
153
+ let nextN = 1;
154
+
155
+ if (fs.existsSync(suggestionsFile)) {
156
+ existingContent = fs.readFileSync(suggestionsFile, 'utf8');
157
+ // Parse existing IDs
158
+ const parsed = parseExistingIds(existingContent);
159
+ for (const id of parsed) {
160
+ existingIds.add(id);
161
+ }
162
+ // Determine the highest existing N for this sprint to continue from
163
+ const sprintPrefix = `SUG-${sprintId}-`;
164
+ let maxN = 0;
165
+ for (const id of parsed) {
166
+ if (id.startsWith(sprintPrefix)) {
167
+ const n = parseInt(id.slice(sprintPrefix.length), 10);
168
+ if (!isNaN(n) && n > maxN) maxN = n;
169
+ }
170
+ }
171
+ nextN = maxN + 1;
172
+ }
173
+
174
+ // Determine which candidates are new (stable hash-based dedup)
175
+ const newEntries = [];
176
+ for (const candidate of candidates) {
177
+ const hashKey = `${candidate.category}|${candidate.item}`;
178
+ const hash = stableHash(hashKey);
179
+ // Check if we already have an entry with this hash in existing content
180
+ // We encode the hash in a comment to enable stable lookup
181
+ const hashMarker = `<!-- hash:${hash} -->`;
182
+ if (existingContent.includes(hashMarker)) {
183
+ continue; // Already captured
184
+ }
185
+ const sugId = `SUG-${sprintId}-${String(nextN).padStart(2, '0')}`;
186
+ nextN++;
187
+ newEntries.push({ sugId, hash, hashMarker, ...candidate });
188
+ }
189
+
190
+ if (newEntries.length === 0) {
191
+ process.stdout.write(
192
+ `Idempotent: no new suggestions to add for sprint ${sprintId}.\n` +
193
+ `Existing file has ${existingIds.size} suggestion(s).\n`
194
+ );
195
+ process.exit(0);
196
+ }
197
+
198
+ // Build new entries markdown
199
+ const timestamp = new Date().toISOString();
200
+ const newEntriesLines = [];
201
+
202
+ // If file doesn't exist yet, write a header
203
+ if (!existingContent) {
204
+ newEntriesLines.push(`# Improvement Suggestions — ${sprintId}`);
205
+ newEntriesLines.push('');
206
+ newEntriesLines.push('Generated by `suggest_improvements.mjs`. Append-only; IDs are stable.');
207
+ newEntriesLines.push(`Vocabulary: Templates | Handoffs | Skills | Process | Tooling`);
208
+ newEntriesLines.push('');
209
+ newEntriesLines.push('---');
210
+ newEntriesLines.push('');
211
+ }
212
+
213
+ for (const entry of newEntries) {
214
+ newEntriesLines.push(`## ${entry.sugId} — ${entry.category}: ${entry.item}`);
215
+ newEntriesLines.push(`${entry.hashMarker}`);
216
+ newEntriesLines.push('');
217
+ newEntriesLines.push(`**Category:** ${entry.category}`);
218
+ newEntriesLines.push(`**Rating:** ${entry.rating}`);
219
+ newEntriesLines.push(`**Added:** ${timestamp}`);
220
+ newEntriesLines.push('');
221
+ if (entry.notes && entry.notes !== '' && entry.notes !== '<notes>') {
222
+ newEntriesLines.push(`**Context from report:** ${entry.notes}`);
223
+ newEntriesLines.push('');
224
+ }
225
+ newEntriesLines.push('**Suggested action:**');
226
+ newEntriesLines.push(`> _(to be filled by orchestrator or next sprint planning)_`);
227
+ newEntriesLines.push('');
228
+ newEntriesLines.push('---');
229
+ newEntriesLines.push('');
230
+ }
231
+
232
+ const appendContent = newEntriesLines.join('\n');
233
+ const finalContent = existingContent
234
+ ? existingContent.trimEnd() + '\n\n' + appendContent
235
+ : appendContent;
236
+
237
+ atomicWrite(suggestionsFile, finalContent);
238
+
239
+ process.stdout.write(
240
+ `suggest_improvements: added ${newEntries.length} new suggestion(s) to ${suggestionsFile}\n`
241
+ );
242
+ for (const e of newEntries) {
243
+ process.stdout.write(` ${e.sugId}: [${e.category}] ${e.item} (${e.rating})\n`);
244
+ }
245
+ }
246
+
247
+ main();
@@ -0,0 +1,27 @@
1
+ # surface-whitelist.txt — Files always admitted by the pre-commit surface gate
2
+ # regardless of the active story's §3.1 declared surface.
3
+ #
4
+ # Format: one pattern per line. Supports bash glob patterns.
5
+ # * matches any non-path-separator sequence
6
+ # ** matches any sequence (including path separators)
7
+ # Lines starting with # are comments; blank lines are ignored.
8
+ #
9
+ # These patterns are matched against repo-root-relative staged file paths.
10
+
11
+ # Build manifest (regenerated by npm run build)
12
+ cleargate-planning/MANIFEST.json
13
+
14
+ # Hook execution logs
15
+ .cleargate/hook-log/*
16
+
17
+ # Token ledger files (auto-generated by SubagentStop hook)
18
+ .cleargate/sprint-runs/**/token-ledger.jsonl
19
+
20
+ # Pending-task sentinel files (auto-generated by PreToolUse hook)
21
+ .cleargate/sprint-runs/**/.pending-task-*.json
22
+
23
+ # Sprint state files (auto-managed)
24
+ .cleargate/sprint-runs/**/state.json
25
+
26
+ # Gate check log (auto-generated)
27
+ .cleargate/hook-log/gate-check.log