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
@@ -247,6 +247,7 @@ const ALL_TOOLS = [
247
247
  'approval', 'rejection',
248
248
  'subagent_invoked', 'subagent_completed',
249
249
  'llm_turn_start', 'llm_turn_end',
250
+ 'prompt_logged',
250
251
  'research_query', 'checkpoint_created', 'rewind',
251
252
  'handoff', 'usage_logged', 'custom'
252
253
  ],
@@ -281,6 +282,174 @@ const ALL_TOOLS = [
281
282
  }
282
283
  }
283
284
  },
285
+ // ─── Usage Logging ──────────────────────────────────────────────────────────
286
+ {
287
+ type: 'function',
288
+ function: {
289
+ name: 'log_usage',
290
+ description: 'Log token usage and estimated cost for the current agent session to the usage log (.jumpstart/usage-log.json). Call this at the end of each phase or significant agent interaction to maintain an audit trail of LLM consumption.',
291
+ parameters: {
292
+ type: 'object',
293
+ properties: {
294
+ phase: {
295
+ type: 'string',
296
+ description: 'Phase identifier (e.g., "phase-0", "scout", "phase-3").'
297
+ },
298
+ agent: {
299
+ type: 'string',
300
+ description: 'Agent name (e.g., "Challenger", "Architect", "Developer").'
301
+ },
302
+ action: {
303
+ type: 'string',
304
+ description: 'Action description (e.g., "generation", "review", "consultation").'
305
+ },
306
+ estimated_tokens: {
307
+ type: 'number',
308
+ description: 'Estimated total token count for this interaction.'
309
+ },
310
+ estimated_cost_usd: {
311
+ type: 'number',
312
+ description: 'Estimated cost in USD (optional — computed from tokens if omitted).'
313
+ },
314
+ model: {
315
+ type: 'string',
316
+ description: 'Model name/ID used for this interaction.'
317
+ },
318
+ metadata: {
319
+ type: 'object',
320
+ description: 'Additional metadata (e.g., { "turns": 12, "artifact": "specs/prd.md" }).'
321
+ }
322
+ },
323
+ required: ['phase', 'agent', 'action', 'estimated_tokens']
324
+ }
325
+ }
326
+ },
327
+ // ─── Item-Tagged Feature Tools ─────────────────────────────────────────
328
+ {
329
+ type: 'function',
330
+ function: {
331
+ name: 'run_revert',
332
+ description: 'Archive a rejected artifact draft and restore the last approved version from git. (Item 40)',
333
+ parameters: {
334
+ type: 'object',
335
+ properties: {
336
+ artifact: { type: 'string', description: 'Path to the artifact file to revert.' },
337
+ reason: { type: 'string', description: 'Reason for reverting.' },
338
+ archive_dir: { type: 'string', description: 'Archive directory (default: .jumpstart/archive).' }
339
+ },
340
+ required: ['artifact']
341
+ }
342
+ }
343
+ },
344
+ {
345
+ type: 'function',
346
+ function: {
347
+ name: 'run_adr_index',
348
+ description: 'Build or search an index of Architecture Decision Records. (Item 51)',
349
+ parameters: {
350
+ type: 'object',
351
+ properties: {
352
+ action: { type: 'string', enum: ['build', 'search'], description: 'Action to perform.' },
353
+ root: { type: 'string', description: 'Project root directory.' },
354
+ query: { type: 'string', description: 'Search query (for search action).' },
355
+ tag: { type: 'string', description: 'Filter by tag (for search action).' }
356
+ },
357
+ required: ['action']
358
+ }
359
+ }
360
+ },
361
+ {
362
+ type: 'function',
363
+ function: {
364
+ name: 'run_complexity',
365
+ description: 'Calculate adaptive planning depth (quick/standard/deep) from project signals. (Item 33)',
366
+ parameters: {
367
+ type: 'object',
368
+ properties: {
369
+ description: { type: 'string', description: 'Free-text problem statement.' },
370
+ root: { type: 'string', description: 'Project root directory.' }
371
+ }
372
+ }
373
+ }
374
+ },
375
+ {
376
+ type: 'function',
377
+ function: {
378
+ name: 'run_crossref',
379
+ description: 'Validate cross-reference links in spec artifacts and detect broken links and orphan sections. (Item 47)',
380
+ parameters: {
381
+ type: 'object',
382
+ properties: {
383
+ specs_dir: { type: 'string', description: 'Path to specs directory (default: specs).' },
384
+ root: { type: 'string', description: 'Project root directory.' }
385
+ }
386
+ }
387
+ }
388
+ },
389
+ {
390
+ type: 'function',
391
+ function: {
392
+ name: 'run_init',
393
+ description: 'Generate initialization configuration based on skill level and project type. (Item 76)',
394
+ parameters: {
395
+ type: 'object',
396
+ properties: {
397
+ skill_level: { type: 'string', enum: ['beginner', 'intermediate', 'expert'], description: 'User skill level.' },
398
+ project_type: { type: 'string', enum: ['greenfield', 'brownfield'], description: 'Project type.' }
399
+ }
400
+ }
401
+ }
402
+ },
403
+ {
404
+ type: 'function',
405
+ function: {
406
+ name: 'run_lock',
407
+ description: 'Manage artifact file locks for concurrent agent access. (Item 45)',
408
+ parameters: {
409
+ type: 'object',
410
+ properties: {
411
+ action: { type: 'string', enum: ['acquire', 'release', 'status', 'list'], description: 'Lock action.' },
412
+ file: { type: 'string', description: 'File path to lock/unlock (required for acquire/release/status).' },
413
+ agent: { type: 'string', description: 'Agent name acquiring/releasing the lock.' }
414
+ },
415
+ required: ['action']
416
+ }
417
+ }
418
+ },
419
+ {
420
+ type: 'function',
421
+ function: {
422
+ name: 'run_timestamp',
423
+ description: 'Generate, validate, or audit UTC timestamps in ISO 8601 format. (Item 60)',
424
+ parameters: {
425
+ type: 'object',
426
+ properties: {
427
+ action: { type: 'string', enum: ['now', 'validate', 'audit'], description: 'Timestamp action.' },
428
+ value: { type: 'string', description: 'Timestamp string to validate (for validate action).' },
429
+ file: { type: 'string', description: 'File path to audit timestamps in (for audit action).' }
430
+ },
431
+ required: ['action']
432
+ }
433
+ }
434
+ },
435
+ {
436
+ type: 'function',
437
+ function: {
438
+ name: 'run_scan',
439
+ description: 'Scan project directory to detect tech stack, dependencies, patterns, and risks. (Item 49)',
440
+ parameters: {
441
+ type: 'object',
442
+ properties: {
443
+ root: { type: 'string', description: 'Project root directory.' },
444
+ ignore: {
445
+ type: 'array',
446
+ items: { type: 'string' },
447
+ description: 'Directories to ignore.'
448
+ }
449
+ }
450
+ }
451
+ }
452
+ },
284
453
  // ─── Quality Gate Tools ────────────────────────────────────────────────────
285
454
  {
286
455
  type: 'function',
@@ -398,7 +567,7 @@ const ALL_TOOLS = [
398
567
  const BASE_TOOLS = [
399
568
  'read_file', 'create_file', 'replace_string_in_file', 'list_dir',
400
569
  'file_search', 'grep_search', 'semantic_search',
401
- 'ask_questions', 'manage_todo_list', 'record_timeline_event'
570
+ 'ask_questions', 'manage_todo_list', 'record_timeline_event', 'log_usage'
402
571
  ];
403
572
 
404
573
  /** Additional tools unlocked per phase */
@@ -407,8 +576,8 @@ const PHASE_TOOL_ADDITIONS = {
407
576
  challenger: [],
408
577
  analyst: [],
409
578
  pm: [],
410
- architect: ['marketplace_install'],
411
- developer: ['run_in_terminal', 'marketplace_install', 'run_secret_scan', 'run_type_check', 'run_smoke_test', 'run_uat_coverage']
579
+ architect: ['marketplace_install', 'run_adr_index', 'run_complexity', 'run_crossref', 'run_lock', 'run_scan', 'run_init'],
580
+ developer: ['run_in_terminal', 'marketplace_install', 'run_secret_scan', 'run_type_check', 'run_smoke_test', 'run_uat_coverage', 'run_revert', 'run_adr_index', 'run_complexity', 'run_crossref', 'run_lock', 'run_timestamp', 'run_scan', 'run_init']
412
581
  };
413
582
 
414
583
  /**
@@ -0,0 +1,150 @@
1
+ /**
2
+ * transcript-ingestion.js — Meeting Transcript Ingestion (Item 74)
3
+ *
4
+ * Convert notes and transcripts into artifact updates,
5
+ * decision proposals, and action items.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/transcript-ingestion.js ingest|extract|list [options]
9
+ *
10
+ * State file: .jumpstart/state/transcripts.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', 'transcripts.json');
19
+
20
+ const ACTION_PATTERNS = [
21
+ /\baction(?:\s+item)?:\s*(.+)/gi,
22
+ /\bTODO:\s*(.+)/gi,
23
+ /\b(?:will|should|needs? to)\s+(.+?)(?:\.|$)/gi
24
+ ];
25
+
26
+ const DECISION_PATTERNS = [
27
+ /\bdecided?\s+(?:to\s+)?(.+?)(?:\.|$)/gi,
28
+ /\bdecision:\s*(.+)/gi,
29
+ /\bagreed\s+(?:to\s+)?(.+?)(?:\.|$)/gi
30
+ ];
31
+
32
+ function defaultState() {
33
+ return { version: '1.0.0', transcripts: [], last_updated: null };
34
+ }
35
+
36
+ function loadState(stateFile) {
37
+ const fp = stateFile || DEFAULT_STATE_FILE;
38
+ if (!fs.existsSync(fp)) return defaultState();
39
+ try { return JSON.parse(fs.readFileSync(fp, 'utf8')); }
40
+ catch { return defaultState(); }
41
+ }
42
+
43
+ function saveState(state, stateFile) {
44
+ const fp = stateFile || DEFAULT_STATE_FILE;
45
+ const dir = path.dirname(fp);
46
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
47
+ state.last_updated = new Date().toISOString();
48
+ fs.writeFileSync(fp, JSON.stringify(state, null, 2) + '\n', 'utf8');
49
+ }
50
+
51
+ /**
52
+ * Ingest a transcript text.
53
+ */
54
+ function ingestTranscript(text, options = {}) {
55
+ if (!text) return { success: false, error: 'Transcript text is required' };
56
+
57
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
58
+ const state = loadState(stateFile);
59
+
60
+ const transcript = {
61
+ id: `TR-${Date.now()}`,
62
+ title: options.title || 'Untitled Meeting',
63
+ source: options.source || 'manual',
64
+ text_length: text.length,
65
+ ingested_at: new Date().toISOString(),
66
+ actions: [],
67
+ decisions: [],
68
+ key_topics: []
69
+ };
70
+
71
+ // Extract action items
72
+ for (const pattern of ACTION_PATTERNS) {
73
+ let match;
74
+ pattern.lastIndex = 0;
75
+ while ((match = pattern.exec(text)) !== null) {
76
+ transcript.actions.push({ text: match[1].trim(), source_pattern: pattern.source.substring(0, 30) });
77
+ }
78
+ }
79
+
80
+ // Extract decisions
81
+ for (const pattern of DECISION_PATTERNS) {
82
+ let match;
83
+ pattern.lastIndex = 0;
84
+ while ((match = pattern.exec(text)) !== null) {
85
+ transcript.decisions.push({ text: match[1].trim() });
86
+ }
87
+ }
88
+
89
+ // Extract key topics (headings or emphasized text)
90
+ const headings = text.match(/^#+\s+(.+)$/gm);
91
+ if (headings) {
92
+ transcript.key_topics = headings.map(h => h.replace(/^#+\s+/, '').trim());
93
+ }
94
+
95
+ state.transcripts.push(transcript);
96
+ saveState(state, stateFile);
97
+
98
+ return { success: true, transcript };
99
+ }
100
+
101
+ /**
102
+ * Extract structured data from a transcript.
103
+ */
104
+ function extractFromTranscript(transcriptId, options = {}) {
105
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
106
+ const state = loadState(stateFile);
107
+
108
+ const transcript = state.transcripts.find(t => t.id === transcriptId);
109
+ if (!transcript) return { success: false, error: `Transcript ${transcriptId} not found` };
110
+
111
+ return {
112
+ success: true,
113
+ id: transcript.id,
114
+ title: transcript.title,
115
+ actions: transcript.actions,
116
+ decisions: transcript.decisions,
117
+ key_topics: transcript.key_topics,
118
+ summary: {
119
+ action_count: transcript.actions.length,
120
+ decision_count: transcript.decisions.length,
121
+ topic_count: transcript.key_topics.length
122
+ }
123
+ };
124
+ }
125
+
126
+ /**
127
+ * List ingested transcripts.
128
+ */
129
+ function listTranscripts(options = {}) {
130
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
131
+ const state = loadState(stateFile);
132
+
133
+ return {
134
+ success: true,
135
+ total: state.transcripts.length,
136
+ transcripts: state.transcripts.map(t => ({
137
+ id: t.id,
138
+ title: t.title,
139
+ actions: t.actions.length,
140
+ decisions: t.decisions.length,
141
+ ingested_at: t.ingested_at
142
+ }))
143
+ };
144
+ }
145
+
146
+ module.exports = {
147
+ ingestTranscript, extractFromTranscript, listTranscripts,
148
+ loadState, saveState, defaultState,
149
+ ACTION_PATTERNS, DECISION_PATTERNS
150
+ };
@@ -0,0 +1,173 @@
1
+ /**
2
+ * vendor-risk.js — Vendor & Dependency Risk Scoring (Item 36)
3
+ *
4
+ * Evaluate OSS and SaaS dependencies for maintenance health,
5
+ * license risk, and supply chain concerns.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/vendor-risk.js scan|assess|report [options]
9
+ *
10
+ * State file: .jumpstart/state/vendor-risk.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', 'vendor-risk.json');
19
+
20
+ const RISK_FACTORS = ['maintenance', 'license', 'security', 'popularity', 'supply-chain'];
21
+
22
+ const LICENSE_RISK = {
23
+ 'MIT': 'low', 'ISC': 'low', 'BSD-2-Clause': 'low', 'BSD-3-Clause': 'low', 'Apache-2.0': 'low',
24
+ 'LGPL-2.1': 'medium', 'LGPL-3.0': 'medium', 'MPL-2.0': 'medium',
25
+ 'GPL-2.0': 'high', 'GPL-3.0': 'high', 'AGPL-3.0': 'high',
26
+ 'SSPL-1.0': 'critical', 'BSL-1.1': 'high', 'UNLICENSED': 'critical', 'unknown': 'high'
27
+ };
28
+
29
+ function defaultState() {
30
+ return {
31
+ version: '1.0.0',
32
+ created_at: new Date().toISOString(),
33
+ last_updated: null,
34
+ assessments: [],
35
+ vendor_catalog: []
36
+ };
37
+ }
38
+
39
+ function loadState(stateFile) {
40
+ const filePath = stateFile || DEFAULT_STATE_FILE;
41
+ if (!fs.existsSync(filePath)) return defaultState();
42
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
43
+ catch { return defaultState(); }
44
+ }
45
+
46
+ function saveState(state, stateFile) {
47
+ const filePath = stateFile || DEFAULT_STATE_FILE;
48
+ const dir = path.dirname(filePath);
49
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
50
+ state.last_updated = new Date().toISOString();
51
+ fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
52
+ }
53
+
54
+ /**
55
+ * Scan project dependencies.
56
+ *
57
+ * @param {string} root - Project root.
58
+ * @param {object} [options]
59
+ * @returns {object}
60
+ */
61
+ function scanDependencies(root, options = {}) {
62
+ const packageFile = path.join(root, 'package.json');
63
+ const dependencies = [];
64
+
65
+ if (fs.existsSync(packageFile)) {
66
+ try {
67
+ const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
68
+ const deps = { ...pkg.dependencies };
69
+ const devDeps = options.includeDevDeps ? pkg.devDependencies || {} : {};
70
+
71
+ for (const [name, version] of Object.entries(deps)) {
72
+ dependencies.push({ name, version, type: 'production', ecosystem: 'npm' });
73
+ }
74
+ for (const [name, version] of Object.entries(devDeps)) {
75
+ dependencies.push({ name, version, type: 'development', ecosystem: 'npm' });
76
+ }
77
+ } catch { /* ignore */ }
78
+ }
79
+
80
+ return { success: true, dependencies, total: dependencies.length };
81
+ }
82
+
83
+ /**
84
+ * Assess risk for a dependency.
85
+ *
86
+ * @param {object} dep - { name, version, license?, last_publish?, weekly_downloads? }
87
+ * @param {object} [options]
88
+ * @returns {object}
89
+ */
90
+ function assessDependency(dep, options = {}) {
91
+ if (!dep || !dep.name) return { success: false, error: 'dep.name is required' };
92
+
93
+ const scores = {};
94
+
95
+ // License risk
96
+ const license = dep.license || 'unknown';
97
+ const licenseRisk = LICENSE_RISK[license] || 'high';
98
+ scores.license = licenseRisk === 'low' ? 90 : licenseRisk === 'medium' ? 60 : licenseRisk === 'high' ? 30 : 10;
99
+
100
+ // Maintenance (based on last publish date if available)
101
+ if (dep.last_publish) {
102
+ const daysSince = Math.floor((Date.now() - new Date(dep.last_publish).getTime()) / (1000 * 60 * 60 * 24));
103
+ scores.maintenance = daysSince < 90 ? 90 : daysSince < 365 ? 60 : daysSince < 730 ? 30 : 10;
104
+ } else {
105
+ scores.maintenance = 50; // unknown
106
+ }
107
+
108
+ // Popularity
109
+ const downloads = dep.weekly_downloads || 0;
110
+ scores.popularity = downloads > 1000000 ? 90 : downloads > 100000 ? 70 : downloads > 10000 ? 50 : 30;
111
+
112
+ // Security (default: no known issues)
113
+ scores.security = dep.known_vulnerabilities ? 20 : 80;
114
+
115
+ // Supply chain
116
+ scores['supply-chain'] = dep.has_lockfile ? 80 : 50;
117
+
118
+ const overall = Math.round(Object.values(scores).reduce((a, b) => a + b, 0) / Object.keys(scores).length);
119
+
120
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
121
+ const state = loadState(stateFile);
122
+
123
+ const assessment = {
124
+ name: dep.name,
125
+ version: dep.version,
126
+ license,
127
+ scores,
128
+ overall,
129
+ risk_level: overall >= 70 ? 'low' : overall >= 50 ? 'medium' : overall >= 30 ? 'high' : 'critical',
130
+ assessed_at: new Date().toISOString()
131
+ };
132
+
133
+ state.assessments.push(assessment);
134
+ saveState(state, stateFile);
135
+
136
+ return { success: true, assessment };
137
+ }
138
+
139
+ /**
140
+ * Generate vendor risk report.
141
+ *
142
+ * @param {object} [options]
143
+ * @returns {object}
144
+ */
145
+ function generateReport(options = {}) {
146
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
147
+ const state = loadState(stateFile);
148
+
149
+ const byRisk = { low: 0, medium: 0, high: 0, critical: 0 };
150
+ for (const a of state.assessments) byRisk[a.risk_level]++;
151
+
152
+ return {
153
+ success: true,
154
+ total_assessed: state.assessments.length,
155
+ by_risk: byRisk,
156
+ high_risk: state.assessments.filter(a => a.risk_level === 'high' || a.risk_level === 'critical'),
157
+ average_score: state.assessments.length > 0
158
+ ? Math.round(state.assessments.reduce((s, a) => s + a.overall, 0) / state.assessments.length)
159
+ : 0,
160
+ assessments: state.assessments
161
+ };
162
+ }
163
+
164
+ module.exports = {
165
+ defaultState,
166
+ loadState,
167
+ saveState,
168
+ scanDependencies,
169
+ assessDependency,
170
+ generateReport,
171
+ RISK_FACTORS,
172
+ LICENSE_RISK
173
+ };
@@ -0,0 +1,174 @@
1
+ /**
2
+ * waiver-workflow.js — Exception & Waiver Workflow (Item 27)
3
+ *
4
+ * Allow formal approval of justified deviations from standards
5
+ * with expiration and owner.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/waiver-workflow.js request|approve|list|expire [options]
9
+ *
10
+ * State file: .jumpstart/state/waivers.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', 'waivers.json');
19
+
20
+ const WAIVER_STATUSES = ['pending', 'approved', 'rejected', 'expired', 'revoked'];
21
+ const WAIVER_CATEGORIES = ['security', 'architecture', 'compliance', 'performance', 'testing', 'documentation', 'other'];
22
+
23
+ function defaultState() {
24
+ return {
25
+ version: '1.0.0',
26
+ created_at: new Date().toISOString(),
27
+ last_updated: null,
28
+ waivers: []
29
+ };
30
+ }
31
+
32
+ function loadState(stateFile) {
33
+ const filePath = stateFile || DEFAULT_STATE_FILE;
34
+ if (!fs.existsSync(filePath)) return defaultState();
35
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
36
+ catch { return defaultState(); }
37
+ }
38
+
39
+ function saveState(state, stateFile) {
40
+ const filePath = stateFile || DEFAULT_STATE_FILE;
41
+ const dir = path.dirname(filePath);
42
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
43
+ state.last_updated = new Date().toISOString();
44
+ fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
45
+ }
46
+
47
+ /**
48
+ * Request a new waiver.
49
+ *
50
+ * @param {object} request - { title, category, justification, owner, expires_in_days? }
51
+ * @param {object} [options]
52
+ * @returns {object}
53
+ */
54
+ function requestWaiver(request, options = {}) {
55
+ if (!request || !request.title || !request.justification || !request.owner) {
56
+ return { success: false, error: 'title, justification, and owner are required' };
57
+ }
58
+
59
+ const category = (request.category || 'other').toLowerCase();
60
+ if (!WAIVER_CATEGORIES.includes(category)) {
61
+ return { success: false, error: `Invalid category. Must be one of: ${WAIVER_CATEGORIES.join(', ')}` };
62
+ }
63
+
64
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
65
+ const state = loadState(stateFile);
66
+
67
+ const expiresInDays = request.expires_in_days || 90;
68
+ const expiresAt = new Date();
69
+ expiresAt.setDate(expiresAt.getDate() + expiresInDays);
70
+
71
+ const waiver = {
72
+ id: `WVR-${Date.now().toString(36).toUpperCase()}`,
73
+ title: request.title,
74
+ category,
75
+ justification: request.justification,
76
+ owner: request.owner,
77
+ status: 'pending',
78
+ requested_at: new Date().toISOString(),
79
+ expires_at: expiresAt.toISOString(),
80
+ approved_by: null,
81
+ approved_at: null,
82
+ conditions: request.conditions || [],
83
+ affected_artifacts: request.affected_artifacts || []
84
+ };
85
+
86
+ state.waivers.push(waiver);
87
+ saveState(state, stateFile);
88
+
89
+ return { success: true, waiver };
90
+ }
91
+
92
+ /**
93
+ * Approve or reject a waiver.
94
+ *
95
+ * @param {string} waiverId
96
+ * @param {string} action - 'approve' or 'reject'
97
+ * @param {object} [options]
98
+ * @returns {object}
99
+ */
100
+ function resolveWaiver(waiverId, action, options = {}) {
101
+ if (!['approve', 'reject'].includes(action)) {
102
+ return { success: false, error: 'action must be "approve" or "reject"' };
103
+ }
104
+
105
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
106
+ const state = loadState(stateFile);
107
+
108
+ const waiver = state.waivers.find(w => w.id === waiverId);
109
+ if (!waiver) return { success: false, error: `Waiver not found: ${waiverId}` };
110
+ if (waiver.status !== 'pending') {
111
+ return { success: false, error: `Waiver is already ${waiver.status}` };
112
+ }
113
+
114
+ waiver.status = action === 'approve' ? 'approved' : 'rejected';
115
+ waiver.approved_by = options.approver || null;
116
+ waiver.approved_at = new Date().toISOString();
117
+
118
+ saveState(state, stateFile);
119
+ return { success: true, waiver };
120
+ }
121
+
122
+ /**
123
+ * Expire outdated waivers.
124
+ *
125
+ * @param {object} [options]
126
+ * @returns {object}
127
+ */
128
+ function expireWaivers(options = {}) {
129
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
130
+ const state = loadState(stateFile);
131
+ const now = new Date();
132
+ let expired = 0;
133
+
134
+ for (const waiver of state.waivers) {
135
+ if (waiver.status === 'approved' && new Date(waiver.expires_at) < now) {
136
+ waiver.status = 'expired';
137
+ expired++;
138
+ }
139
+ }
140
+
141
+ saveState(state, stateFile);
142
+ return { success: true, expired, total_waivers: state.waivers.length };
143
+ }
144
+
145
+ /**
146
+ * List waivers with optional filter.
147
+ *
148
+ * @param {object} [filter] - { status?, category?, owner? }
149
+ * @param {object} [options]
150
+ * @returns {object}
151
+ */
152
+ function listWaivers(filter = {}, options = {}) {
153
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
154
+ const state = loadState(stateFile);
155
+ let waivers = state.waivers;
156
+
157
+ if (filter.status) waivers = waivers.filter(w => w.status === filter.status);
158
+ if (filter.category) waivers = waivers.filter(w => w.category === filter.category);
159
+ if (filter.owner) waivers = waivers.filter(w => w.owner === filter.owner);
160
+
161
+ return { success: true, waivers, total: waivers.length };
162
+ }
163
+
164
+ module.exports = {
165
+ defaultState,
166
+ loadState,
167
+ saveState,
168
+ requestWaiver,
169
+ resolveWaiver,
170
+ expireWaivers,
171
+ listWaivers,
172
+ WAIVER_STATUSES,
173
+ WAIVER_CATEGORIES
174
+ };