jumpstart-mode 1.1.12 → 1.1.13

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 (146) hide show
  1. package/.github/agents/jumpstart-adversary.agent.md +2 -1
  2. package/.github/agents/jumpstart-architect.agent.md +5 -6
  3. package/.github/agents/jumpstart-challenger.agent.md +2 -1
  4. package/.github/agents/jumpstart-devops.agent.md +2 -2
  5. package/.github/agents/jumpstart-diagram-verifier.agent.md +2 -1
  6. package/.github/agents/jumpstart-maintenance.agent.md +1 -0
  7. package/.github/agents/jumpstart-performance.agent.md +1 -0
  8. package/.github/agents/jumpstart-pm.agent.md +1 -1
  9. package/.github/agents/jumpstart-refactor.agent.md +1 -0
  10. package/.github/agents/jumpstart-requirements-extractor.agent.md +1 -0
  11. package/.github/agents/jumpstart-researcher.agent.md +1 -0
  12. package/.github/agents/jumpstart-retrospective.agent.md +1 -0
  13. package/.github/agents/jumpstart-reviewer.agent.md +2 -0
  14. package/.github/agents/jumpstart-scout.agent.md +1 -1
  15. package/.github/agents/jumpstart-scrum-master.agent.md +1 -0
  16. package/.github/agents/jumpstart-security.agent.md +2 -1
  17. package/.github/agents/jumpstart-tech-writer.agent.md +1 -0
  18. package/.github/workflows/quality.yml +19 -2
  19. package/.jumpstart/agents/analyst.md +38 -0
  20. package/.jumpstart/agents/architect.md +38 -0
  21. package/.jumpstart/agents/challenger.md +38 -0
  22. package/.jumpstart/agents/developer.md +41 -0
  23. package/.jumpstart/agents/pm.md +38 -0
  24. package/.jumpstart/agents/scout.md +33 -0
  25. package/.jumpstart/agents/ux-designer.md +4 -0
  26. package/.jumpstart/config.yaml +24 -0
  27. package/.jumpstart/schemas/timeline.schema.json +1 -0
  28. package/.jumpstart/skills/skill-creator/SKILL.md +485 -357
  29. package/.jumpstart/skills/skill-creator/agents/analyzer.md +274 -0
  30. package/.jumpstart/skills/skill-creator/agents/comparator.md +202 -0
  31. package/.jumpstart/skills/skill-creator/agents/grader.md +223 -0
  32. package/.jumpstart/skills/skill-creator/assets/eval_review.html +146 -0
  33. package/.jumpstart/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  34. package/.jumpstart/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  35. package/.jumpstart/skills/skill-creator/references/schemas.md +430 -0
  36. package/.jumpstart/skills/skill-creator/scripts/__init__.py +0 -0
  37. package/.jumpstart/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  38. package/.jumpstart/skills/skill-creator/scripts/generate_report.py +326 -0
  39. package/.jumpstart/skills/skill-creator/scripts/improve_description.py +247 -0
  40. package/.jumpstart/skills/skill-creator/scripts/package_skill.py +136 -110
  41. package/.jumpstart/skills/skill-creator/scripts/run_eval.py +310 -0
  42. package/.jumpstart/skills/skill-creator/scripts/run_loop.py +328 -0
  43. package/.jumpstart/skills/skill-creator/scripts/utils.py +47 -0
  44. package/.jumpstart/state/timeline.json +659 -0
  45. package/.jumpstart/usage-log.json +74 -3
  46. package/README.md +62 -1
  47. package/bin/cli.js +3217 -1
  48. package/bin/headless-runner.js +62 -2
  49. package/bin/lib/agent-checkpoint.js +168 -0
  50. package/bin/lib/ai-evaluation.js +104 -0
  51. package/bin/lib/ai-intake.js +152 -0
  52. package/bin/lib/ambiguity-heatmap.js +152 -0
  53. package/bin/lib/artifact-comparison.js +104 -0
  54. package/bin/lib/ast-edit-engine.js +157 -0
  55. package/bin/lib/backlog-sync.js +338 -0
  56. package/bin/lib/bcdr-planning.js +158 -0
  57. package/bin/lib/bidirectional-trace.js +199 -0
  58. package/bin/lib/branch-workflow.js +266 -0
  59. package/bin/lib/cab-output.js +119 -0
  60. package/bin/lib/chat-integration.js +122 -0
  61. package/bin/lib/ci-cd-integration.js +208 -0
  62. package/bin/lib/codebase-retrieval.js +125 -0
  63. package/bin/lib/collaboration.js +168 -0
  64. package/bin/lib/compliance-packs.js +213 -0
  65. package/bin/lib/context-chunker.js +128 -0
  66. package/bin/lib/context-onboarding.js +122 -0
  67. package/bin/lib/contract-first.js +124 -0
  68. package/bin/lib/cost-router.js +148 -0
  69. package/bin/lib/credential-boundary.js +155 -0
  70. package/bin/lib/data-classification.js +180 -0
  71. package/bin/lib/data-contracts.js +129 -0
  72. package/bin/lib/db-evolution.js +158 -0
  73. package/bin/lib/decision-conflicts.js +299 -0
  74. package/bin/lib/delivery-confidence.js +361 -0
  75. package/bin/lib/dependency-upgrade.js +153 -0
  76. package/bin/lib/design-system.js +133 -0
  77. package/bin/lib/deterministic-artifacts.js +151 -0
  78. package/bin/lib/diagram-studio.js +115 -0
  79. package/bin/lib/domain-ontology.js +140 -0
  80. package/bin/lib/ea-review-packet.js +151 -0
  81. package/bin/lib/enterprise-search.js +123 -0
  82. package/bin/lib/enterprise-templates.js +140 -0
  83. package/bin/lib/environment-promotion.js +220 -0
  84. package/bin/lib/estimation-studio.js +130 -0
  85. package/bin/lib/event-modeling.js +133 -0
  86. package/bin/lib/evidence-collector.js +179 -0
  87. package/bin/lib/finops-planner.js +182 -0
  88. package/bin/lib/fitness-functions.js +279 -0
  89. package/bin/lib/focus.js +448 -0
  90. package/bin/lib/governance-dashboard.js +165 -0
  91. package/bin/lib/guided-handoff.js +120 -0
  92. package/bin/lib/impact-analysis.js +190 -0
  93. package/bin/lib/incident-feedback.js +157 -0
  94. package/bin/lib/integrate.js +1 -1
  95. package/bin/lib/knowledge-graph.js +122 -0
  96. package/bin/lib/legacy-modernizer.js +160 -0
  97. package/bin/lib/migration-planner.js +144 -0
  98. package/bin/lib/model-governance.js +185 -0
  99. package/bin/lib/model-router.js +144 -0
  100. package/bin/lib/multi-repo.js +272 -0
  101. package/bin/lib/next-phase.js +53 -8
  102. package/bin/lib/ops-ownership.js +152 -0
  103. package/bin/lib/parallel-agents.js +257 -0
  104. package/bin/lib/pattern-library.js +115 -0
  105. package/bin/lib/persona-packs.js +99 -0
  106. package/bin/lib/plan-executor.js +366 -0
  107. package/bin/lib/platform-engineering.js +119 -0
  108. package/bin/lib/playback-summaries.js +126 -0
  109. package/bin/lib/policy-engine.js +240 -0
  110. package/bin/lib/portfolio-reporting.js +357 -0
  111. package/bin/lib/pr-package.js +197 -0
  112. package/bin/lib/project-memory.js +235 -0
  113. package/bin/lib/prompt-governance.js +130 -0
  114. package/bin/lib/promptless-mode.js +128 -0
  115. package/bin/lib/quality-graph.js +193 -0
  116. package/bin/lib/raci-matrix.js +188 -0
  117. package/bin/lib/refactor-planner.js +167 -0
  118. package/bin/lib/reference-architectures.js +304 -0
  119. package/bin/lib/release-readiness.js +171 -0
  120. package/bin/lib/repo-graph.js +262 -0
  121. package/bin/lib/requirements-baseline.js +358 -0
  122. package/bin/lib/risk-register.js +211 -0
  123. package/bin/lib/role-approval.js +249 -0
  124. package/bin/lib/role-views.js +142 -0
  125. package/bin/lib/root-cause-analysis.js +132 -0
  126. package/bin/lib/runtime-debugger.js +154 -0
  127. package/bin/lib/safe-rename.js +135 -0
  128. package/bin/lib/semantic-diff.js +335 -0
  129. package/bin/lib/sla-slo.js +210 -0
  130. package/bin/lib/spec-comments.js +147 -0
  131. package/bin/lib/spec-maturity.js +287 -0
  132. package/bin/lib/sre-integration.js +154 -0
  133. package/bin/lib/structured-elicitation.js +174 -0
  134. package/bin/lib/telemetry-feedback.js +118 -0
  135. package/bin/lib/test-generator.js +146 -0
  136. package/bin/lib/timeline.js +2 -1
  137. package/bin/lib/tool-bridge.js +107 -0
  138. package/bin/lib/tool-guardrails.js +139 -0
  139. package/bin/lib/tool-schemas.js +172 -3
  140. package/bin/lib/transcript-ingestion.js +150 -0
  141. package/bin/lib/vendor-risk.js +173 -0
  142. package/bin/lib/waiver-workflow.js +174 -0
  143. package/bin/lib/web-dashboard.js +126 -0
  144. package/bin/lib/workshop-mode.js +165 -0
  145. package/bin/lib/workstream-ownership.js +104 -0
  146. package/package.json +1 -1
@@ -0,0 +1,118 @@
1
+ /**
2
+ * telemetry-feedback.js — Production Telemetry Feedback Loop (Item 95)
3
+ *
4
+ * Feed real performance and reliability data back into
5
+ * future planning and architecture.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/telemetry-feedback.js ingest|analyze|report [options]
9
+ *
10
+ * State file: .jumpstart/state/telemetry-feedback.json
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const DEFAULT_STATE_FILE = path.join('.jumpstart', 'state', 'telemetry-feedback.json');
19
+
20
+ const METRIC_TYPES = ['latency', 'error-rate', 'throughput', 'availability', 'saturation', 'cost'];
21
+
22
+ function defaultState() {
23
+ return { version: '1.0.0', metrics: [], insights: [], last_updated: null };
24
+ }
25
+
26
+ function loadState(stateFile) {
27
+ const fp = stateFile || DEFAULT_STATE_FILE;
28
+ if (!fs.existsSync(fp)) return defaultState();
29
+ try { return JSON.parse(fs.readFileSync(fp, 'utf8')); }
30
+ catch { return defaultState(); }
31
+ }
32
+
33
+ function saveState(state, stateFile) {
34
+ const fp = stateFile || DEFAULT_STATE_FILE;
35
+ const dir = path.dirname(fp);
36
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
37
+ state.last_updated = new Date().toISOString();
38
+ fs.writeFileSync(fp, JSON.stringify(state, null, 2) + '\n', 'utf8');
39
+ }
40
+
41
+ function ingestMetric(name, type, value, options = {}) {
42
+ if (!name || !type) return { success: false, error: 'name and type are required' };
43
+ if (!METRIC_TYPES.includes(type)) {
44
+ return { success: false, error: `Unknown type: ${type}. Valid: ${METRIC_TYPES.join(', ')}` };
45
+ }
46
+
47
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
48
+ const state = loadState(stateFile);
49
+
50
+ const metric = {
51
+ id: `TEL-${Date.now()}`,
52
+ name,
53
+ type,
54
+ value,
55
+ unit: options.unit || null,
56
+ service: options.service || null,
57
+ timestamp: new Date().toISOString()
58
+ };
59
+
60
+ state.metrics.push(metric);
61
+ saveState(state, stateFile);
62
+
63
+ return { success: true, metric };
64
+ }
65
+
66
+ function analyzeMetrics(options = {}) {
67
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
68
+ const state = loadState(stateFile);
69
+
70
+ const byType = {};
71
+ for (const m of state.metrics) {
72
+ if (!byType[m.type]) byType[m.type] = [];
73
+ byType[m.type].push(m.value);
74
+ }
75
+
76
+ const analysis = {};
77
+ for (const [type, values] of Object.entries(byType)) {
78
+ const nums = values.filter(v => typeof v === 'number');
79
+ if (nums.length > 0) {
80
+ analysis[type] = {
81
+ count: nums.length,
82
+ avg: Math.round((nums.reduce((a, b) => a + b, 0) / nums.length) * 100) / 100,
83
+ min: Math.min(...nums),
84
+ max: Math.max(...nums)
85
+ };
86
+ }
87
+ }
88
+
89
+ return { success: true, total_metrics: state.metrics.length, analysis };
90
+ }
91
+
92
+ function generateFeedbackReport(options = {}) {
93
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
94
+ const state = loadState(stateFile);
95
+ const analysis = analyzeMetrics(options);
96
+
97
+ const recommendations = [];
98
+ if (analysis.analysis.latency && analysis.analysis.latency.avg > 500) {
99
+ recommendations.push('High average latency detected — consider caching or query optimization');
100
+ }
101
+ if (analysis.analysis['error-rate'] && analysis.analysis['error-rate'].avg > 5) {
102
+ recommendations.push('Elevated error rate — review error handling and resilience patterns');
103
+ }
104
+
105
+ return {
106
+ success: true,
107
+ total_metrics: state.metrics.length,
108
+ total_insights: state.insights.length,
109
+ analysis: analysis.analysis,
110
+ recommendations
111
+ };
112
+ }
113
+
114
+ module.exports = {
115
+ ingestMetric, analyzeMetrics, generateFeedbackReport,
116
+ loadState, saveState, defaultState,
117
+ METRIC_TYPES
118
+ };
@@ -0,0 +1,146 @@
1
+ /**
2
+ * test-generator.js — Test Generation Tied to Acceptance Criteria (Item 44)
3
+ *
4
+ * Auto-generate unit, integration, API, UI, and contract tests
5
+ * from requirements.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/test-generator.js generate|coverage [options]
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const TEST_TYPES = ['unit', 'integration', 'api', 'ui', 'contract', 'e2e'];
17
+
18
+ const TEST_FRAMEWORKS = {
19
+ javascript: { framework: 'vitest', extension: '.test.js', import: "import { describe, it, expect } from 'vitest';" },
20
+ typescript: { framework: 'vitest', extension: '.test.ts', import: "import { describe, it, expect } from 'vitest';" },
21
+ python: { framework: 'pytest', extension: '_test.py', import: 'import pytest' }
22
+ };
23
+
24
+ /**
25
+ * Extract acceptance criteria from PRD content.
26
+ *
27
+ * @param {string} content - PRD markdown content.
28
+ * @returns {object[]}
29
+ */
30
+ function extractCriteria(content) {
31
+ const criteria = [];
32
+ const lines = content.split('\n');
33
+ let currentStory = null;
34
+
35
+ for (const line of lines) {
36
+ const storyMatch = line.match(/\*\*(E\d+-S\d+)\*\*/);
37
+ if (storyMatch) currentStory = storyMatch[1];
38
+
39
+ const givenMatch = line.match(/^\s*[-*]\s*(Given\s+.+)/i);
40
+ const whenMatch = line.match(/^\s*[-*]\s*(When\s+.+)/i);
41
+ const thenMatch = line.match(/^\s*[-*]\s*(Then\s+.+)/i);
42
+ const acMatch = line.match(/^\s*[-*]\s*AC\d*:\s*(.+)/i);
43
+
44
+ if (givenMatch || whenMatch || thenMatch || acMatch) {
45
+ criteria.push({
46
+ story: currentStory,
47
+ criterion: (givenMatch || whenMatch || thenMatch || acMatch)[1].trim(),
48
+ type: givenMatch ? 'given' : whenMatch ? 'when' : thenMatch ? 'then' : 'acceptance'
49
+ });
50
+ }
51
+ }
52
+
53
+ return criteria;
54
+ }
55
+
56
+ /**
57
+ * Generate test stubs from acceptance criteria.
58
+ *
59
+ * @param {object[]} criteria - Extracted criteria.
60
+ * @param {object} [options]
61
+ * @returns {object}
62
+ */
63
+ function generateTestStubs(criteria, options = {}) {
64
+ const language = options.language || 'javascript';
65
+ const fwConfig = TEST_FRAMEWORKS[language];
66
+ if (!fwConfig) {
67
+ return { success: false, error: `Unsupported language: ${language}` };
68
+ }
69
+
70
+ const byStory = {};
71
+ for (const c of criteria) {
72
+ const story = c.story || 'general';
73
+ if (!byStory[story]) byStory[story] = [];
74
+ byStory[story].push(c);
75
+ }
76
+
77
+ const testFiles = [];
78
+ for (const [story, storyCriteria] of Object.entries(byStory)) {
79
+ const fileName = `${story.toLowerCase().replace(/[^a-z0-9]/g, '-')}${fwConfig.extension}`;
80
+ const tests = storyCriteria.map(c => {
81
+ const testName = c.criterion.replace(/'/g, "\\'").substring(0, 100);
82
+ return ` it('${testName}', () => {\n // TODO: Implement test for ${c.type}\n expect(true).toBe(true);\n });`;
83
+ });
84
+
85
+ const content = `${fwConfig.import}\n\ndescribe('${story}', () => {\n${tests.join('\n\n')}\n});\n`;
86
+ testFiles.push({ fileName, content, story, test_count: storyCriteria.length });
87
+ }
88
+
89
+ return {
90
+ success: true,
91
+ total_criteria: criteria.length,
92
+ test_files: testFiles.length,
93
+ files: testFiles,
94
+ framework: fwConfig.framework
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Check test coverage against acceptance criteria.
100
+ *
101
+ * @param {string} root - Project root.
102
+ * @param {object} [options]
103
+ * @returns {object}
104
+ */
105
+ function checkCoverage(root, options = {}) {
106
+ const prdFile = path.join(root, 'specs', 'prd.md');
107
+ if (!fs.existsSync(prdFile)) {
108
+ return { success: false, error: 'PRD not found at specs/prd.md' };
109
+ }
110
+
111
+ const prdContent = fs.readFileSync(prdFile, 'utf8');
112
+ const criteria = extractCriteria(prdContent);
113
+
114
+ // Check test directory for matching tests
115
+ const testDir = path.join(root, 'tests');
116
+ let testContent = '';
117
+ if (fs.existsSync(testDir)) {
118
+ for (const entry of fs.readdirSync(testDir)) {
119
+ if (entry.endsWith('.test.js') || entry.endsWith('.test.ts')) {
120
+ try { testContent += fs.readFileSync(path.join(testDir, entry), 'utf8') + '\n'; }
121
+ catch { /* skip */ }
122
+ }
123
+ }
124
+ }
125
+
126
+ const covered = criteria.filter(c => {
127
+ const terms = c.criterion.toLowerCase().split(/\s+/).filter(t => t.length > 3);
128
+ return terms.some(t => testContent.toLowerCase().includes(t));
129
+ });
130
+
131
+ return {
132
+ success: true,
133
+ total_criteria: criteria.length,
134
+ covered: covered.length,
135
+ coverage: criteria.length > 0 ? Math.round((covered.length / criteria.length) * 100) : 0,
136
+ uncovered: criteria.filter(c => !covered.includes(c))
137
+ };
138
+ }
139
+
140
+ module.exports = {
141
+ extractCriteria,
142
+ generateTestStubs,
143
+ checkCoverage,
144
+ TEST_TYPES,
145
+ TEST_FRAMEWORKS
146
+ };
@@ -36,6 +36,7 @@ const EVENT_TYPES = [
36
36
  'approval', 'rejection',
37
37
  'subagent_invoked', 'subagent_completed',
38
38
  'llm_turn_start', 'llm_turn_end',
39
+ 'prompt_logged',
39
40
  'research_query', 'checkpoint_created', 'rewind',
40
41
  'handoff', 'usage_logged', 'custom'
41
42
  ];
@@ -780,7 +781,7 @@ function _shouldCapture(eventType, capture) {
780
781
  case 'research_query':
781
782
  return capture.research;
782
783
  default:
783
- return true; // Always capture: phase_start, phase_end, handoff, checkpoint, rewind, usage_logged, custom
784
+ return true; // Always capture: phase_start, phase_end, prompt_logged, handoff, checkpoint, rewind, usage_logged, custom
784
785
  }
785
786
  }
786
787
 
@@ -255,6 +255,113 @@ function createToolBridge(options = {}) {
255
255
  return { success: !!evt, event_id: evt ? evt.id : null };
256
256
  },
257
257
 
258
+ /**
259
+ * Log token usage and cost to .jumpstart/usage-log.json.
260
+ */
261
+ async log_usage(args) {
262
+ try {
263
+ const { logUsage } = await import('./usage.js');
264
+ const logPath = path.join(workspaceDir, '.jumpstart', 'usage-log.json');
265
+ const entry = {
266
+ phase: args.phase || 'unknown',
267
+ agent: args.agent || 'unknown',
268
+ action: args.action || 'unknown',
269
+ estimated_tokens: args.estimated_tokens || 0,
270
+ estimated_cost_usd: args.estimated_cost_usd || (args.estimated_tokens || 0) * 0.000002,
271
+ model: args.model || null,
272
+ metadata: args.metadata || null
273
+ };
274
+ const log = logUsage(logPath, entry);
275
+ return { success: true, total_tokens: log.total_tokens, total_entries: log.entries.length };
276
+ } catch (err) {
277
+ return { success: false, error: err.message };
278
+ }
279
+ },
280
+
281
+ // ── Item-Tagged Feature Tool Handlers ──────────────────────────────────
282
+
283
+ async run_revert(args) {
284
+ try {
285
+ const { revertArtifact } = await import('./revert.js');
286
+ return revertArtifact({ artifact: args.artifact, reason: args.reason, archive_dir: args.archive_dir });
287
+ } catch (err) {
288
+ return { success: false, error: err.message };
289
+ }
290
+ },
291
+
292
+ async run_adr_index(args) {
293
+ try {
294
+ const { buildIndex, searchIndex } = await import('./adr-index.js');
295
+ const root = args.root || workspaceDir;
296
+ if (args.action === 'search') {
297
+ const index = buildIndex(root);
298
+ return searchIndex(index, args.query || '', { tag: args.tag });
299
+ }
300
+ return buildIndex(root);
301
+ } catch (err) {
302
+ return { success: false, error: err.message };
303
+ }
304
+ },
305
+
306
+ async run_complexity(args) {
307
+ try {
308
+ const { calculateComplexity } = await import('./complexity.js');
309
+ return calculateComplexity({ description: args.description || '', root: args.root || workspaceDir });
310
+ } catch (err) {
311
+ return { success: false, error: err.message };
312
+ }
313
+ },
314
+
315
+ async run_crossref(args) {
316
+ try {
317
+ const { validateCrossRefs } = await import('./crossref.js');
318
+ return validateCrossRefs(args.specs_dir || 'specs', args.root || workspaceDir);
319
+ } catch (err) {
320
+ return { success: false, error: err.message };
321
+ }
322
+ },
323
+
324
+ async run_init(args) {
325
+ try {
326
+ const { generateInitConfig } = await import('./init.js');
327
+ return generateInitConfig({ skill_level: args.skill_level || 'intermediate', project_type: args.project_type || 'greenfield' });
328
+ } catch (err) {
329
+ return { success: false, error: err.message };
330
+ }
331
+ },
332
+
333
+ async run_lock(args) {
334
+ try {
335
+ const { acquireLock, releaseLock, lockStatus, listLocks } = await import('./locks.js');
336
+ if (args.action === 'acquire') return acquireLock(args.file, args.agent || 'headless');
337
+ if (args.action === 'release') return releaseLock(args.file, args.agent || 'headless');
338
+ if (args.action === 'status') return lockStatus(args.file);
339
+ return listLocks();
340
+ } catch (err) {
341
+ return { success: false, error: err.message };
342
+ }
343
+ },
344
+
345
+ async run_timestamp(args) {
346
+ try {
347
+ const { now, validate, audit } = await import('./timestamps.js');
348
+ if (args.action === 'validate') return validate(args.value);
349
+ if (args.action === 'audit') return audit(args.file);
350
+ return { timestamp: now() };
351
+ } catch (err) {
352
+ return { success: false, error: err.message };
353
+ }
354
+ },
355
+
356
+ async run_scan(args) {
357
+ try {
358
+ const { scan } = await import('./scanner.js');
359
+ return scan({ root: args.root || workspaceDir, ignore: args.ignore });
360
+ } catch (err) {
361
+ return { success: false, error: err.message };
362
+ }
363
+ },
364
+
258
365
  // ── Quality Gate Tool Handlers ──────────────────────────────────────────
259
366
 
260
367
  async run_secret_scan(args) {
@@ -0,0 +1,139 @@
1
+ /**
2
+ * tool-guardrails.js — Tool Execution Guardrails (Item 58)
3
+ *
4
+ * Validate risky file operations, deletions, schema changes,
5
+ * and wide-scope edits before execution.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/tool-guardrails.js check|policy|report [options]
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const RISK_RULES = [
17
+ { id: 'delete-protection', pattern: /^(?:rm|del|remove)\s+/i, risk: 'high', description: 'File deletion operation' },
18
+ { id: 'recursive-delete', pattern: /rm\s+-rf?\s/i, risk: 'critical', description: 'Recursive deletion' },
19
+ { id: 'schema-change', pattern: /(?:ALTER|DROP|TRUNCATE)\s+(?:TABLE|DATABASE|SCHEMA)/i, risk: 'high', description: 'Database schema modification' },
20
+ { id: 'config-write', pattern: /(?:\.env|config|secrets?)\s*$/i, risk: 'high', description: 'Configuration file modification' },
21
+ { id: 'wide-glob', pattern: /\*\*\/\*|\*\.\*/i, risk: 'medium', description: 'Wide glob pattern' },
22
+ { id: 'sudo-usage', pattern: /\bsudo\b/i, risk: 'critical', description: 'Elevated privilege usage' },
23
+ { id: 'network-call', pattern: /\b(?:curl|wget|fetch)\s+http/i, risk: 'medium', description: 'External network call' },
24
+ { id: 'git-force', pattern: /git\s+(?:push\s+--force|reset\s+--hard)/i, risk: 'high', description: 'Force git operation' }
25
+ ];
26
+
27
+ const PROTECTED_PATHS = [
28
+ '.env', '.env.local', '.env.production',
29
+ '.git/', 'node_modules/',
30
+ 'package-lock.json', 'yarn.lock',
31
+ '.jumpstart/state/'
32
+ ];
33
+
34
+ /**
35
+ * Check a command or operation for guardrail violations.
36
+ *
37
+ * @param {string} operation - Command or file path.
38
+ * @param {object} [options]
39
+ * @returns {object}
40
+ */
41
+ function checkOperation(operation, options = {}) {
42
+ if (!operation) return { success: false, error: 'operation is required' };
43
+
44
+ const violations = [];
45
+
46
+ // Check against risk rules
47
+ for (const rule of RISK_RULES) {
48
+ if (rule.pattern.test(operation)) {
49
+ violations.push({
50
+ rule_id: rule.id,
51
+ risk: rule.risk,
52
+ description: rule.description,
53
+ matched: operation.substring(0, 100)
54
+ });
55
+ }
56
+ }
57
+
58
+ // Check against protected paths
59
+ for (const pp of PROTECTED_PATHS) {
60
+ if (operation.includes(pp)) {
61
+ violations.push({
62
+ rule_id: 'protected-path',
63
+ risk: 'high',
64
+ description: `Operation targets protected path: ${pp}`,
65
+ matched: pp
66
+ });
67
+ }
68
+ }
69
+
70
+ const maxRisk = violations.reduce((max, v) => {
71
+ const riskOrder = { critical: 4, high: 3, medium: 2, low: 1 };
72
+ return riskOrder[v.risk] > riskOrder[max] ? v.risk : max;
73
+ }, 'low');
74
+
75
+ return {
76
+ success: true,
77
+ operation: operation.substring(0, 200),
78
+ allowed: violations.filter(v => v.risk === 'critical').length === 0,
79
+ requires_approval: violations.some(v => v.risk === 'high' || v.risk === 'critical'),
80
+ risk_level: violations.length > 0 ? maxRisk : 'none',
81
+ violations,
82
+ total_violations: violations.length
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Validate a file operation (create/edit/delete).
88
+ *
89
+ * @param {string} action - 'create' | 'edit' | 'delete'
90
+ * @param {string} filePath
91
+ * @param {object} [options]
92
+ * @returns {object}
93
+ */
94
+ function validateFileOperation(action, filePath, options = {}) {
95
+ const warnings = [];
96
+
97
+ if (action === 'delete') {
98
+ warnings.push({ level: 'high', message: `Deleting file: ${filePath}` });
99
+
100
+ for (const pp of PROTECTED_PATHS) {
101
+ if (filePath.includes(pp)) {
102
+ return {
103
+ success: true,
104
+ allowed: false,
105
+ reason: `Cannot delete protected path: ${pp}`,
106
+ warnings
107
+ };
108
+ }
109
+ }
110
+ }
111
+
112
+ if (action === 'edit') {
113
+ const ext = path.extname(filePath).toLowerCase();
114
+ if (['.env', '.pem', '.key'].includes(ext)) {
115
+ warnings.push({ level: 'high', message: 'Editing sensitive file type' });
116
+ }
117
+ }
118
+
119
+ const linesChanged = options.lines_changed || 0;
120
+ if (linesChanged > 100) {
121
+ warnings.push({ level: 'medium', message: `Large edit: ${linesChanged} lines changed` });
122
+ }
123
+
124
+ return {
125
+ success: true,
126
+ allowed: true,
127
+ action,
128
+ file: filePath,
129
+ warnings,
130
+ requires_review: warnings.some(w => w.level === 'high')
131
+ };
132
+ }
133
+
134
+ module.exports = {
135
+ checkOperation,
136
+ validateFileOperation,
137
+ RISK_RULES,
138
+ PROTECTED_PATHS
139
+ };