@ai-sdlc/orchestrator 0.4.0 → 0.6.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 (72) hide show
  1. package/dist/action-enforcement.d.ts +26 -0
  2. package/dist/action-enforcement.js +70 -0
  3. package/dist/adapters.d.ts +18 -3
  4. package/dist/adapters.js +92 -2
  5. package/dist/admission-score.d.ts +58 -0
  6. package/dist/admission-score.js +164 -0
  7. package/dist/cli/commands/init.js +4 -8
  8. package/dist/cli/commands/run.js +2 -2
  9. package/dist/config.d.ts +3 -0
  10. package/dist/config.js +14 -5
  11. package/dist/cycle-utils.d.ts +51 -0
  12. package/dist/cycle-utils.js +77 -0
  13. package/dist/defaults.d.ts +5 -0
  14. package/dist/defaults.js +5 -0
  15. package/dist/execute.d.ts +5 -2
  16. package/dist/execute.js +212 -62
  17. package/dist/fix-ci.js +45 -13
  18. package/dist/fix-review.d.ts +66 -0
  19. package/dist/fix-review.js +441 -0
  20. package/dist/index.d.ts +14 -4
  21. package/dist/index.js +18 -3
  22. package/dist/orchestrator.d.ts +1 -1
  23. package/dist/orchestrator.js +31 -9
  24. package/dist/pipeline-cycle-detector.d.ts +70 -0
  25. package/dist/pipeline-cycle-detector.js +111 -0
  26. package/dist/plugin.d.ts +9 -3
  27. package/dist/priority.d.ts +28 -0
  28. package/dist/priority.js +230 -0
  29. package/dist/review.d.ts +31 -0
  30. package/dist/review.js +74 -0
  31. package/dist/runners/claude-code.js +367 -35
  32. package/dist/runners/codex.js +15 -4
  33. package/dist/runners/copilot.js +15 -4
  34. package/dist/runners/cursor.js +15 -4
  35. package/dist/runners/generic-llm.js +1 -1
  36. package/dist/runners/index.d.ts +3 -1
  37. package/dist/runners/index.js +2 -0
  38. package/dist/runners/review-agent.d.ts +47 -0
  39. package/dist/runners/review-agent.js +220 -0
  40. package/dist/runners/security-triage.d.ts +43 -0
  41. package/dist/runners/security-triage.js +158 -0
  42. package/dist/runners/types.d.ts +24 -1
  43. package/dist/security.d.ts +8 -3
  44. package/dist/security.js +13 -2
  45. package/dist/shared.d.ts +17 -0
  46. package/dist/shared.js +27 -0
  47. package/dist/state/index.d.ts +1 -1
  48. package/dist/state/schema.d.ts +4 -1
  49. package/dist/state/schema.js +89 -1
  50. package/dist/state/store.d.ts +31 -1
  51. package/dist/state/store.js +208 -13
  52. package/dist/state/types.d.ts +52 -0
  53. package/dist/triage.d.ts +36 -0
  54. package/dist/triage.js +133 -0
  55. package/dist/types.d.ts +1 -1
  56. package/dist/watch.d.ts +6 -2
  57. package/dist/watch.js +34 -6
  58. package/dist/workflow-patterns/artifact-writer.d.ts +16 -0
  59. package/dist/workflow-patterns/artifact-writer.js +34 -0
  60. package/dist/workflow-patterns/classifiers.d.ts +10 -0
  61. package/dist/workflow-patterns/classifiers.js +72 -0
  62. package/dist/workflow-patterns/detector.d.ts +27 -0
  63. package/dist/workflow-patterns/detector.js +186 -0
  64. package/dist/workflow-patterns/index.d.ts +8 -0
  65. package/dist/workflow-patterns/index.js +7 -0
  66. package/dist/workflow-patterns/proposal-generator.d.ts +15 -0
  67. package/dist/workflow-patterns/proposal-generator.js +183 -0
  68. package/dist/workflow-patterns/telemetry-ingest.d.ts +27 -0
  69. package/dist/workflow-patterns/telemetry-ingest.js +103 -0
  70. package/dist/workflow-patterns/types.d.ts +61 -0
  71. package/dist/workflow-patterns/types.js +11 -0
  72. package/package.json +4 -2
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SQLite DDL and migrations for the state store.
3
3
  */
4
- export declare const CURRENT_SCHEMA_VERSION = 6;
4
+ export declare const CURRENT_SCHEMA_VERSION = 9;
5
5
  export declare const SCHEMA_DDL = "\nCREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY,\n applied_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS complexity_profile (\n id INTEGER PRIMARY KEY,\n repo_path TEXT NOT NULL,\n score REAL NOT NULL,\n files_count INTEGER,\n modules_count INTEGER,\n dependency_count INTEGER,\n analyzed_at TEXT DEFAULT (datetime('now')),\n raw_data TEXT\n);\n\nCREATE TABLE IF NOT EXISTS episodic_memory (\n id INTEGER PRIMARY KEY,\n issue_number INTEGER,\n pr_number INTEGER,\n pipeline_type TEXT NOT NULL,\n outcome TEXT NOT NULL,\n duration_ms INTEGER,\n files_changed INTEGER,\n error_message TEXT,\n metadata TEXT,\n created_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS autonomy_ledger (\n id INTEGER PRIMARY KEY,\n agent_name TEXT NOT NULL UNIQUE,\n current_level INTEGER DEFAULT 0,\n total_tasks INTEGER DEFAULT 0,\n success_count INTEGER DEFAULT 0,\n failure_count INTEGER DEFAULT 0,\n last_task_at TEXT,\n metrics TEXT\n);\n\nCREATE TABLE IF NOT EXISTS pipeline_runs (\n id INTEGER PRIMARY KEY,\n run_id TEXT NOT NULL UNIQUE,\n issue_number INTEGER,\n pr_number INTEGER,\n pipeline_type TEXT NOT NULL,\n status TEXT NOT NULL,\n current_stage TEXT,\n started_at TEXT DEFAULT (datetime('now')),\n completed_at TEXT,\n result TEXT,\n gate_results TEXT\n);\n\nCREATE TABLE IF NOT EXISTS conventions (\n id INTEGER PRIMARY KEY,\n category TEXT NOT NULL,\n pattern TEXT NOT NULL,\n confidence REAL,\n examples TEXT,\n detected_at TEXT DEFAULT (datetime('now'))\n);\n";
6
6
  export interface Migration {
7
7
  version: number;
@@ -12,5 +12,8 @@ export declare const MIGRATION_V3 = "\n-- Cost tracking\nCREATE TABLE IF NOT EXI
12
12
  export declare const MIGRATION_V4 = "\n-- Handoff audit trail\nCREATE TABLE IF NOT EXISTS handoff_events (\n id INTEGER PRIMARY KEY,\n run_id TEXT NOT NULL,\n from_agent TEXT NOT NULL,\n to_agent TEXT NOT NULL,\n payload_hash TEXT,\n validation_result TEXT NOT NULL,\n error_message TEXT,\n created_at TEXT DEFAULT (datetime('now'))\n);\nCREATE INDEX IF NOT EXISTS idx_handoff_events_run ON handoff_events(run_id);\n";
13
13
  export declare const MIGRATION_V5 = "\n-- Deployment records\nCREATE TABLE IF NOT EXISTS deployments (\n id INTEGER PRIMARY KEY,\n deployment_id TEXT NOT NULL UNIQUE,\n target_name TEXT NOT NULL,\n provider TEXT NOT NULL,\n version TEXT NOT NULL,\n environment TEXT NOT NULL,\n state TEXT NOT NULL,\n url TEXT,\n error TEXT,\n started_at TEXT DEFAULT (datetime('now')),\n completed_at TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_deployments_target ON deployments(target_name);\nCREATE INDEX IF NOT EXISTS idx_deployments_env ON deployments(environment);\n\n-- Rollout step records\nCREATE TABLE IF NOT EXISTS rollout_steps (\n id INTEGER PRIMARY KEY,\n deployment_id TEXT NOT NULL,\n step_number INTEGER NOT NULL,\n weight_percent INTEGER NOT NULL,\n state TEXT NOT NULL,\n metrics_snapshot TEXT,\n started_at TEXT DEFAULT (datetime('now')),\n completed_at TEXT,\n FOREIGN KEY (deployment_id) REFERENCES deployments(deployment_id)\n);\nCREATE INDEX IF NOT EXISTS idx_rollout_steps_deployment ON rollout_steps(deployment_id);\n\n-- Audit entries (indexed, queryable)\nCREATE TABLE IF NOT EXISTS audit_entries (\n id INTEGER PRIMARY KEY,\n entry_id TEXT NOT NULL UNIQUE,\n actor TEXT NOT NULL,\n action TEXT NOT NULL,\n resource_type TEXT,\n resource_id TEXT,\n detail TEXT,\n hash TEXT,\n previous_hash TEXT,\n signature TEXT,\n created_at TEXT DEFAULT (datetime('now'))\n);\nCREATE INDEX IF NOT EXISTS idx_audit_entries_actor ON audit_entries(actor);\nCREATE INDEX IF NOT EXISTS idx_audit_entries_action ON audit_entries(action);\nCREATE INDEX IF NOT EXISTS idx_audit_entries_resource ON audit_entries(resource_type, resource_id);\nCREATE INDEX IF NOT EXISTS idx_audit_entries_created ON audit_entries(created_at);\n";
14
14
  export declare const MIGRATION_V6 = "\n-- Cost governance: add stage_name and cache_read_tokens to cost_ledger\nALTER TABLE cost_ledger ADD COLUMN stage_name TEXT;\nALTER TABLE cost_ledger ADD COLUMN cache_read_tokens INTEGER DEFAULT 0;\n";
15
+ export declare const MIGRATION_V7 = "\n-- String issue IDs\nALTER TABLE pipeline_runs ADD COLUMN issue_id TEXT;\nALTER TABLE episodic_memory ADD COLUMN issue_id TEXT;\nALTER TABLE cost_ledger ADD COLUMN issue_id TEXT;\nALTER TABLE routing_history ADD COLUMN issue_id TEXT;\n";
16
+ export declare const MIGRATION_V8 = "\n-- Priority calibration table (RFC-0005 PPA)\nCREATE TABLE IF NOT EXISTS priority_calibration (\n id INTEGER PRIMARY KEY,\n issue_id TEXT NOT NULL,\n priority_composite REAL NOT NULL,\n priority_confidence REAL NOT NULL,\n priority_dimensions TEXT,\n actual_complexity INTEGER,\n files_changed INTEGER,\n outcome TEXT,\n sampled_at TEXT DEFAULT (datetime('now'))\n);\nCREATE INDEX IF NOT EXISTS idx_priority_calibration_issue ON priority_calibration(issue_id);\nCREATE INDEX IF NOT EXISTS idx_priority_calibration_sampled ON priority_calibration(sampled_at);\n\n-- Extend episodic_memory with priority columns\nALTER TABLE episodic_memory ADD COLUMN priority_composite REAL;\nALTER TABLE episodic_memory ADD COLUMN priority_confidence REAL;\n";
17
+ export declare const MIGRATION_V9 = "\n-- Workflow pattern detection tables\n\n-- Tool sequence events captured by PostToolUse hook\nCREATE TABLE IF NOT EXISTS tool_sequence_events (\n id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n tool_name TEXT NOT NULL,\n action_canonical TEXT NOT NULL,\n project_path TEXT,\n timestamp TEXT NOT NULL,\n ingested_at TEXT DEFAULT (datetime('now'))\n);\nCREATE INDEX IF NOT EXISTS idx_tool_seq_session ON tool_sequence_events(session_id);\nCREATE INDEX IF NOT EXISTS idx_tool_seq_ts ON tool_sequence_events(timestamp);\n\n-- Detected workflow patterns (from n-gram mining)\nCREATE TABLE IF NOT EXISTS workflow_patterns (\n id INTEGER PRIMARY KEY,\n pattern_hash TEXT NOT NULL UNIQUE,\n pattern_type TEXT NOT NULL,\n sequence_json TEXT NOT NULL,\n frequency INTEGER NOT NULL,\n session_count INTEGER NOT NULL,\n confidence REAL NOT NULL,\n first_seen TEXT,\n last_seen TEXT,\n status TEXT DEFAULT 'detected',\n detected_at TEXT DEFAULT (datetime('now'))\n);\nCREATE INDEX IF NOT EXISTS idx_workflow_patterns_status ON workflow_patterns(status);\n\n-- Automation proposals for human review\nCREATE TABLE IF NOT EXISTS pattern_proposals (\n id INTEGER PRIMARY KEY,\n pattern_id INTEGER NOT NULL,\n proposal_type TEXT NOT NULL,\n artifact_type TEXT NOT NULL,\n artifact_path TEXT,\n draft_content TEXT NOT NULL,\n confidence REAL NOT NULL,\n status TEXT DEFAULT 'pending',\n reviewed_at TEXT,\n reviewer_reason TEXT,\n created_at TEXT DEFAULT (datetime('now')),\n FOREIGN KEY (pattern_id) REFERENCES workflow_patterns(id)\n);\nCREATE INDEX IF NOT EXISTS idx_pattern_proposals_status ON pattern_proposals(status);\n";
15
18
  export declare const MIGRATIONS: Migration[];
16
19
  //# sourceMappingURL=schema.d.ts.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SQLite DDL and migrations for the state store.
3
3
  */
4
- export const CURRENT_SCHEMA_VERSION = 6;
4
+ export const CURRENT_SCHEMA_VERSION = 9;
5
5
  export const SCHEMA_DDL = `
6
6
  CREATE TABLE IF NOT EXISTS schema_version (
7
7
  version INTEGER PRIMARY KEY,
@@ -235,6 +235,82 @@ export const MIGRATION_V6 = `
235
235
  ALTER TABLE cost_ledger ADD COLUMN stage_name TEXT;
236
236
  ALTER TABLE cost_ledger ADD COLUMN cache_read_tokens INTEGER DEFAULT 0;
237
237
  `;
238
+ export const MIGRATION_V7 = `
239
+ -- String issue IDs
240
+ ALTER TABLE pipeline_runs ADD COLUMN issue_id TEXT;
241
+ ALTER TABLE episodic_memory ADD COLUMN issue_id TEXT;
242
+ ALTER TABLE cost_ledger ADD COLUMN issue_id TEXT;
243
+ ALTER TABLE routing_history ADD COLUMN issue_id TEXT;
244
+ `;
245
+ export const MIGRATION_V8 = `
246
+ -- Priority calibration table (RFC-0005 PPA)
247
+ CREATE TABLE IF NOT EXISTS priority_calibration (
248
+ id INTEGER PRIMARY KEY,
249
+ issue_id TEXT NOT NULL,
250
+ priority_composite REAL NOT NULL,
251
+ priority_confidence REAL NOT NULL,
252
+ priority_dimensions TEXT,
253
+ actual_complexity INTEGER,
254
+ files_changed INTEGER,
255
+ outcome TEXT,
256
+ sampled_at TEXT DEFAULT (datetime('now'))
257
+ );
258
+ CREATE INDEX IF NOT EXISTS idx_priority_calibration_issue ON priority_calibration(issue_id);
259
+ CREATE INDEX IF NOT EXISTS idx_priority_calibration_sampled ON priority_calibration(sampled_at);
260
+
261
+ -- Extend episodic_memory with priority columns
262
+ ALTER TABLE episodic_memory ADD COLUMN priority_composite REAL;
263
+ ALTER TABLE episodic_memory ADD COLUMN priority_confidence REAL;
264
+ `;
265
+ export const MIGRATION_V9 = `
266
+ -- Workflow pattern detection tables
267
+
268
+ -- Tool sequence events captured by PostToolUse hook
269
+ CREATE TABLE IF NOT EXISTS tool_sequence_events (
270
+ id INTEGER PRIMARY KEY,
271
+ session_id TEXT NOT NULL,
272
+ tool_name TEXT NOT NULL,
273
+ action_canonical TEXT NOT NULL,
274
+ project_path TEXT,
275
+ timestamp TEXT NOT NULL,
276
+ ingested_at TEXT DEFAULT (datetime('now'))
277
+ );
278
+ CREATE INDEX IF NOT EXISTS idx_tool_seq_session ON tool_sequence_events(session_id);
279
+ CREATE INDEX IF NOT EXISTS idx_tool_seq_ts ON tool_sequence_events(timestamp);
280
+
281
+ -- Detected workflow patterns (from n-gram mining)
282
+ CREATE TABLE IF NOT EXISTS workflow_patterns (
283
+ id INTEGER PRIMARY KEY,
284
+ pattern_hash TEXT NOT NULL UNIQUE,
285
+ pattern_type TEXT NOT NULL,
286
+ sequence_json TEXT NOT NULL,
287
+ frequency INTEGER NOT NULL,
288
+ session_count INTEGER NOT NULL,
289
+ confidence REAL NOT NULL,
290
+ first_seen TEXT,
291
+ last_seen TEXT,
292
+ status TEXT DEFAULT 'detected',
293
+ detected_at TEXT DEFAULT (datetime('now'))
294
+ );
295
+ CREATE INDEX IF NOT EXISTS idx_workflow_patterns_status ON workflow_patterns(status);
296
+
297
+ -- Automation proposals for human review
298
+ CREATE TABLE IF NOT EXISTS pattern_proposals (
299
+ id INTEGER PRIMARY KEY,
300
+ pattern_id INTEGER NOT NULL,
301
+ proposal_type TEXT NOT NULL,
302
+ artifact_type TEXT NOT NULL,
303
+ artifact_path TEXT,
304
+ draft_content TEXT NOT NULL,
305
+ confidence REAL NOT NULL,
306
+ status TEXT DEFAULT 'pending',
307
+ reviewed_at TEXT,
308
+ reviewer_reason TEXT,
309
+ created_at TEXT DEFAULT (datetime('now')),
310
+ FOREIGN KEY (pattern_id) REFERENCES workflow_patterns(id)
311
+ );
312
+ CREATE INDEX IF NOT EXISTS idx_pattern_proposals_status ON pattern_proposals(status);
313
+ `;
238
314
  export const MIGRATIONS = [
239
315
  {
240
316
  version: 1,
@@ -260,5 +336,17 @@ export const MIGRATIONS = [
260
336
  version: 6,
261
337
  sql: MIGRATION_V6,
262
338
  },
339
+ {
340
+ version: 7,
341
+ sql: MIGRATION_V7,
342
+ },
343
+ {
344
+ version: 8,
345
+ sql: MIGRATION_V8,
346
+ },
347
+ {
348
+ version: 9,
349
+ sql: MIGRATION_V9,
350
+ },
263
351
  ];
264
352
  //# sourceMappingURL=schema.js.map
@@ -5,7 +5,7 @@
5
5
  * optional — the orchestrator works without it.
6
6
  */
7
7
  import type BetterSqlite3 from 'better-sqlite3';
8
- import type { ComplexityProfile, EpisodicRecord, AutonomyLedgerEntry, PipelineRun, PipelineRunStatus, Convention, HotspotRecord, RoutingDecision, CostLedgerEntry, GateThresholdOverride, AutonomyEvent, HandoffEvent, DeploymentRecord, DeploymentRecordState, RolloutStepRecord, AuditEntryRecord } from './types.js';
8
+ import type { ComplexityProfile, EpisodicRecord, AutonomyLedgerEntry, PipelineRun, PipelineRunStatus, Convention, HotspotRecord, RoutingDecision, CostLedgerEntry, GateThresholdOverride, AutonomyEvent, HandoffEvent, DeploymentRecord, DeploymentRecordState, RolloutStepRecord, AuditEntryRecord, PriorityCalibrationSample, ToolSequenceEvent, WorkflowPattern, PatternProposal } from './types.js';
9
9
  export declare class StateStore {
10
10
  private db;
11
11
  constructor(db: BetterSqlite3.Database);
@@ -102,8 +102,38 @@ export declare class StateStore {
102
102
  }): AuditEntryRecord[];
103
103
  getAuditEntry(entryId: string): AuditEntryRecord | undefined;
104
104
  private mapAuditEntry;
105
+ savePrioritySample(sample: PriorityCalibrationSample): number;
106
+ getPrioritySamples(opts?: {
107
+ since?: string;
108
+ limit?: number;
109
+ }): PriorityCalibrationSample[];
110
+ /**
111
+ * Compute a calibration coefficient from historical priority samples.
112
+ * Returns 1.0 when no data is available.
113
+ * When data exists, compares predicted priority ordering with actual outcomes
114
+ * and adjusts the coefficient to correct systematic over/under-scoring.
115
+ */
116
+ computeCalibrationCoefficient(opts?: {
117
+ since?: string;
118
+ }): number;
105
119
  /** Expose the underlying database for direct queries (e.g. dashboard). */
106
120
  getDatabase(): BetterSqlite3.Database;
121
+ saveToolSequenceEvent(event: ToolSequenceEvent): number;
122
+ saveToolSequenceEvents(events: ToolSequenceEvent[]): number;
123
+ getToolSequenceEvents(opts?: {
124
+ sessionId?: string;
125
+ since?: string;
126
+ limit?: number;
127
+ }): ToolSequenceEvent[];
128
+ saveWorkflowPattern(pattern: WorkflowPattern): number;
129
+ getWorkflowPatterns(opts?: {
130
+ status?: string;
131
+ }): WorkflowPattern[];
132
+ savePatternProposal(proposal: PatternProposal): number;
133
+ getPatternProposals(opts?: {
134
+ status?: string;
135
+ }): PatternProposal[];
136
+ updateProposalStatus(id: number, status: string, reason?: string): void;
107
137
  close(): void;
108
138
  }
109
139
  //# sourceMappingURL=store.d.ts.map
@@ -82,11 +82,12 @@ export class StateStore {
82
82
  // ── Episodic Memory ──────────────────────────────────────────────
83
83
  saveEpisodicRecord(record) {
84
84
  const stmt = this.db.prepare(`
85
- INSERT INTO episodic_memory (issue_number, pr_number, pipeline_type, outcome, duration_ms, files_changed, error_message, metadata,
86
- agent_name, complexity_score, routing_strategy, gate_pass_count, gate_fail_count, cost_usd, is_regression, related_episodes)
87
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
85
+ INSERT INTO episodic_memory (issue_id, issue_number, pr_number, pipeline_type, outcome, duration_ms, files_changed, error_message, metadata,
86
+ agent_name, complexity_score, routing_strategy, gate_pass_count, gate_fail_count, cost_usd, is_regression, related_episodes,
87
+ priority_composite, priority_confidence)
88
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
88
89
  `);
89
- const result = stmt.run(record.issueNumber ?? null, record.prNumber ?? null, record.pipelineType, record.outcome, record.durationMs ?? null, record.filesChanged ?? null, record.errorMessage ?? null, record.metadata ?? null, record.agentName ?? null, record.complexityScore ?? null, record.routingStrategy ?? null, record.gatePassCount ?? null, record.gateFailCount ?? null, record.costUsd ?? null, record.isRegression ?? 0, record.relatedEpisodes ?? null);
90
+ const result = stmt.run(record.issueId ?? null, record.issueNumber ?? null, record.prNumber ?? null, record.pipelineType, record.outcome, record.durationMs ?? null, record.filesChanged ?? null, record.errorMessage ?? null, record.metadata ?? null, record.agentName ?? null, record.complexityScore ?? null, record.routingStrategy ?? null, record.gatePassCount ?? null, record.gateFailCount ?? null, record.costUsd ?? null, record.isRegression ?? 0, record.relatedEpisodes ?? null, record.priorityComposite ?? null, record.priorityConfidence ?? null);
90
91
  return Number(result.lastInsertRowid);
91
92
  }
92
93
  getEpisodicRecords(issueNumber, limit = 50) {
@@ -99,6 +100,7 @@ export class StateStore {
99
100
  mapEpisodicRecord(row) {
100
101
  return {
101
102
  id: row.id,
103
+ issueId: row.issue_id,
102
104
  issueNumber: row.issue_number,
103
105
  prNumber: row.pr_number,
104
106
  pipelineType: row.pipeline_type,
@@ -116,6 +118,8 @@ export class StateStore {
116
118
  costUsd: row.cost_usd,
117
119
  isRegression: row.is_regression,
118
120
  relatedEpisodes: row.related_episodes,
121
+ priorityComposite: row.priority_composite,
122
+ priorityConfidence: row.priority_confidence,
119
123
  };
120
124
  }
121
125
  // ── Autonomy Ledger ──────────────────────────────────────────────
@@ -172,11 +176,11 @@ export class StateStore {
172
176
  // ── Pipeline Runs ────────────────────────────────────────────────
173
177
  savePipelineRun(run) {
174
178
  const stmt = this.db.prepare(`
175
- INSERT INTO pipeline_runs (run_id, issue_number, pr_number, pipeline_type, status, current_stage, result, gate_results,
179
+ INSERT INTO pipeline_runs (run_id, issue_id, issue_number, pr_number, pipeline_type, status, current_stage, result, gate_results,
176
180
  cost_usd, tokens_used, model, agent_name, complexity_score)
177
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
181
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
178
182
  `);
179
- const result = stmt.run(run.runId, run.issueNumber ?? null, run.prNumber ?? null, run.pipelineType, run.status, run.currentStage ?? null, run.result ?? null, run.gateResults ?? null, run.costUsd ?? 0, run.tokensUsed ?? 0, run.model ?? null, run.agentName ?? null, run.complexityScore ?? null);
183
+ const result = stmt.run(run.runId, run.issueId ?? null, run.issueNumber ?? null, run.prNumber ?? null, run.pipelineType, run.status, run.currentStage ?? null, run.result ?? null, run.gateResults ?? null, run.costUsd ?? 0, run.tokensUsed ?? 0, run.model ?? null, run.agentName ?? null, run.complexityScore ?? null);
180
184
  return Number(result.lastInsertRowid);
181
185
  }
182
186
  updatePipelineRunStatus(runId, status, opts) {
@@ -210,6 +214,7 @@ export class StateStore {
210
214
  return {
211
215
  id: row.id,
212
216
  runId: row.run_id,
217
+ issueId: row.issue_id,
213
218
  issueNumber: row.issue_number,
214
219
  prNumber: row.pr_number,
215
220
  pipelineType: row.pipeline_type,
@@ -283,10 +288,10 @@ export class StateStore {
283
288
  // ── Routing History ───────────────────────────────────────────
284
289
  saveRoutingDecision(decision) {
285
290
  const stmt = this.db.prepare(`
286
- INSERT INTO routing_history (issue_number, task_complexity, codebase_complexity, routing_strategy, agent_name, reason)
287
- VALUES (?, ?, ?, ?, ?, ?)
291
+ INSERT INTO routing_history (issue_id, issue_number, task_complexity, codebase_complexity, routing_strategy, agent_name, reason)
292
+ VALUES (?, ?, ?, ?, ?, ?, ?)
288
293
  `);
289
- const result = stmt.run(decision.issueNumber ?? null, decision.taskComplexity, decision.codebaseComplexity, decision.routingStrategy, decision.agentName ?? null, decision.reason ?? null);
294
+ const result = stmt.run(decision.issueId ?? null, decision.issueNumber ?? null, decision.taskComplexity, decision.codebaseComplexity, decision.routingStrategy, decision.agentName ?? null, decision.reason ?? null);
290
295
  return Number(result.lastInsertRowid);
291
296
  }
292
297
  getRoutingHistory(limit = 50) {
@@ -298,6 +303,7 @@ export class StateStore {
298
303
  mapRoutingDecision(row) {
299
304
  return {
300
305
  id: row.id,
306
+ issueId: row.issue_id,
301
307
  issueNumber: row.issue_number,
302
308
  taskComplexity: row.task_complexity,
303
309
  codebaseComplexity: row.codebase_complexity,
@@ -310,10 +316,10 @@ export class StateStore {
310
316
  // ── Cost Ledger ────────────────────────────────────────────────
311
317
  saveCostEntry(entry) {
312
318
  const stmt = this.db.prepare(`
313
- INSERT INTO cost_ledger (run_id, agent_name, pipeline_type, model, input_tokens, output_tokens, total_tokens, cost_usd, issue_number, pr_number, stage_name, cache_read_tokens)
314
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
319
+ INSERT INTO cost_ledger (run_id, agent_name, pipeline_type, model, input_tokens, output_tokens, total_tokens, cost_usd, issue_id, issue_number, pr_number, stage_name, cache_read_tokens)
320
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
315
321
  `);
316
- const result = stmt.run(entry.runId, entry.agentName, entry.pipelineType, entry.model ?? null, entry.inputTokens ?? 0, entry.outputTokens ?? 0, entry.totalTokens ?? 0, entry.costUsd ?? 0, entry.issueNumber ?? null, entry.prNumber ?? null, entry.stageName ?? null, entry.cacheReadTokens ?? 0);
322
+ const result = stmt.run(entry.runId, entry.agentName, entry.pipelineType, entry.model ?? null, entry.inputTokens ?? 0, entry.outputTokens ?? 0, entry.totalTokens ?? 0, entry.costUsd ?? 0, entry.issueId ?? null, entry.issueNumber ?? null, entry.prNumber ?? null, entry.stageName ?? null, entry.cacheReadTokens ?? 0);
317
323
  return Number(result.lastInsertRowid);
318
324
  }
319
325
  getCostEntries(opts) {
@@ -359,6 +365,7 @@ export class StateStore {
359
365
  outputTokens: row.output_tokens,
360
366
  totalTokens: row.total_tokens,
361
367
  costUsd: row.cost_usd,
368
+ issueId: row.issue_id,
362
369
  issueNumber: row.issue_number,
363
370
  prNumber: row.pr_number,
364
371
  stageName: row.stage_name,
@@ -633,11 +640,199 @@ export class StateStore {
633
640
  createdAt: row.created_at,
634
641
  };
635
642
  }
643
+ // ── Priority Calibration ──────────────────────────────────────────
644
+ savePrioritySample(sample) {
645
+ const stmt = this.db.prepare(`
646
+ INSERT INTO priority_calibration (issue_id, priority_composite, priority_confidence, priority_dimensions, actual_complexity, files_changed, outcome)
647
+ VALUES (?, ?, ?, ?, ?, ?, ?)
648
+ `);
649
+ const result = stmt.run(sample.issueId, sample.priorityComposite, sample.priorityConfidence, sample.priorityDimensions ?? null, sample.actualComplexity ?? null, sample.filesChanged ?? null, sample.outcome ?? null);
650
+ return Number(result.lastInsertRowid);
651
+ }
652
+ getPrioritySamples(opts) {
653
+ const conditions = [];
654
+ const params = [];
655
+ if (opts?.since) {
656
+ conditions.push('sampled_at >= ?');
657
+ params.push(opts.since);
658
+ }
659
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
660
+ const limit = opts?.limit ?? 500;
661
+ params.push(limit);
662
+ const rows = this.db
663
+ .prepare(`SELECT * FROM priority_calibration ${where} ORDER BY sampled_at DESC LIMIT ?`)
664
+ .all(...params);
665
+ return rows.map((r) => ({
666
+ id: r.id,
667
+ issueId: r.issue_id,
668
+ priorityComposite: r.priority_composite,
669
+ priorityConfidence: r.priority_confidence,
670
+ priorityDimensions: r.priority_dimensions,
671
+ actualComplexity: r.actual_complexity,
672
+ filesChanged: r.files_changed,
673
+ outcome: r.outcome,
674
+ sampledAt: r.sampled_at,
675
+ }));
676
+ }
677
+ /**
678
+ * Compute a calibration coefficient from historical priority samples.
679
+ * Returns 1.0 when no data is available.
680
+ * When data exists, compares predicted priority ordering with actual outcomes
681
+ * and adjusts the coefficient to correct systematic over/under-scoring.
682
+ */
683
+ computeCalibrationCoefficient(opts) {
684
+ const samples = this.getPrioritySamples({ since: opts?.since });
685
+ if (samples.length === 0)
686
+ return 1.0;
687
+ // Filter to samples that have both predicted priority and actual outcome
688
+ const scored = samples.filter((s) => s.outcome === 'success' || s.outcome === 'failure');
689
+ if (scored.length === 0)
690
+ return 1.0;
691
+ // Compute average composite for successes vs failures
692
+ const successes = scored.filter((s) => s.outcome === 'success');
693
+ const failures = scored.filter((s) => s.outcome === 'failure');
694
+ if (successes.length === 0 || failures.length === 0)
695
+ return 1.0;
696
+ const avgSuccess = successes.reduce((sum, s) => sum + s.priorityComposite, 0) / successes.length;
697
+ const avgFailure = failures.reduce((sum, s) => sum + s.priorityComposite, 0) / failures.length;
698
+ // If high-priority items are failing more than low-priority ones,
699
+ // reduce the coefficient to dampen over-scoring; otherwise increase.
700
+ // The ratio is clamped to [0.7, 1.3] per PPA spec.
701
+ if (avgSuccess === 0 && avgFailure === 0)
702
+ return 1.0;
703
+ const ratio = avgSuccess > 0 ? avgFailure / avgSuccess : 1.0;
704
+ // ratio > 1 means failures had higher scores → over-scoring → reduce
705
+ // ratio < 1 means successes had higher scores → well-calibrated or under → increase slightly
706
+ const coefficient = 1.0 / Math.max(ratio, 0.01);
707
+ return Math.min(1.3, Math.max(0.7, coefficient));
708
+ }
636
709
  // ── Utilities ────────────────────────────────────────────────────
637
710
  /** Expose the underlying database for direct queries (e.g. dashboard). */
638
711
  getDatabase() {
639
712
  return this.db;
640
713
  }
714
+ // ── Workflow Pattern Detection ─────────────────────────────────────
715
+ saveToolSequenceEvent(event) {
716
+ const stmt = this.db.prepare(`
717
+ INSERT INTO tool_sequence_events (session_id, tool_name, action_canonical, project_path, timestamp)
718
+ VALUES (?, ?, ?, ?, ?)
719
+ `);
720
+ const result = stmt.run(event.sessionId, event.toolName, event.actionCanonical, event.projectPath ?? null, event.timestamp);
721
+ return Number(result.lastInsertRowid);
722
+ }
723
+ saveToolSequenceEvents(events) {
724
+ const stmt = this.db.prepare(`
725
+ INSERT INTO tool_sequence_events (session_id, tool_name, action_canonical, project_path, timestamp)
726
+ VALUES (?, ?, ?, ?, ?)
727
+ `);
728
+ const tx = this.db.transaction((items) => {
729
+ for (const e of items) {
730
+ stmt.run(e.sessionId, e.toolName, e.actionCanonical, e.projectPath ?? null, e.timestamp);
731
+ }
732
+ return items.length;
733
+ });
734
+ return tx(events);
735
+ }
736
+ getToolSequenceEvents(opts) {
737
+ const conditions = [];
738
+ const params = [];
739
+ if (opts?.sessionId) {
740
+ conditions.push('session_id = ?');
741
+ params.push(opts.sessionId);
742
+ }
743
+ if (opts?.since) {
744
+ conditions.push('timestamp >= ?');
745
+ params.push(opts.since);
746
+ }
747
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
748
+ const limit = opts?.limit ?? 10000;
749
+ params.push(limit);
750
+ const rows = this.db
751
+ .prepare(`SELECT * FROM tool_sequence_events ${where} ORDER BY timestamp ASC LIMIT ?`)
752
+ .all(...params);
753
+ return rows.map((r) => ({
754
+ id: r.id,
755
+ sessionId: r.session_id,
756
+ toolName: r.tool_name,
757
+ actionCanonical: r.action_canonical,
758
+ projectPath: r.project_path,
759
+ timestamp: r.timestamp,
760
+ ingestedAt: r.ingested_at,
761
+ }));
762
+ }
763
+ saveWorkflowPattern(pattern) {
764
+ const stmt = this.db.prepare(`
765
+ INSERT OR REPLACE INTO workflow_patterns
766
+ (pattern_hash, pattern_type, sequence_json, frequency, session_count, confidence, first_seen, last_seen, status)
767
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
768
+ `);
769
+ const result = stmt.run(pattern.patternHash, pattern.patternType, pattern.sequenceJson, pattern.frequency, pattern.sessionCount, pattern.confidence, pattern.firstSeen ?? null, pattern.lastSeen ?? null, pattern.status);
770
+ return Number(result.lastInsertRowid);
771
+ }
772
+ getWorkflowPatterns(opts) {
773
+ const conditions = [];
774
+ const params = [];
775
+ if (opts?.status) {
776
+ conditions.push('status = ?');
777
+ params.push(opts.status);
778
+ }
779
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
780
+ const rows = this.db
781
+ .prepare(`SELECT * FROM workflow_patterns ${where} ORDER BY confidence DESC`)
782
+ .all(...params);
783
+ return rows.map((r) => ({
784
+ id: r.id,
785
+ patternHash: r.pattern_hash,
786
+ patternType: r.pattern_type,
787
+ sequenceJson: r.sequence_json,
788
+ frequency: r.frequency,
789
+ sessionCount: r.session_count,
790
+ confidence: r.confidence,
791
+ firstSeen: r.first_seen,
792
+ lastSeen: r.last_seen,
793
+ status: r.status,
794
+ detectedAt: r.detected_at,
795
+ }));
796
+ }
797
+ savePatternProposal(proposal) {
798
+ const stmt = this.db.prepare(`
799
+ INSERT INTO pattern_proposals
800
+ (pattern_id, proposal_type, artifact_type, artifact_path, draft_content, confidence, status)
801
+ VALUES (?, ?, ?, ?, ?, ?, ?)
802
+ `);
803
+ const result = stmt.run(proposal.patternId, proposal.proposalType, proposal.artifactType, proposal.artifactPath ?? null, proposal.draftContent, proposal.confidence, proposal.status);
804
+ return Number(result.lastInsertRowid);
805
+ }
806
+ getPatternProposals(opts) {
807
+ const conditions = [];
808
+ const params = [];
809
+ if (opts?.status) {
810
+ conditions.push('status = ?');
811
+ params.push(opts.status);
812
+ }
813
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
814
+ const rows = this.db
815
+ .prepare(`SELECT * FROM pattern_proposals ${where} ORDER BY confidence DESC`)
816
+ .all(...params);
817
+ return rows.map((r) => ({
818
+ id: r.id,
819
+ patternId: r.pattern_id,
820
+ proposalType: r.proposal_type,
821
+ artifactType: r.artifact_type,
822
+ artifactPath: r.artifact_path,
823
+ draftContent: r.draft_content,
824
+ confidence: r.confidence,
825
+ status: r.status,
826
+ reviewedAt: r.reviewed_at,
827
+ reviewerReason: r.reviewer_reason,
828
+ createdAt: r.created_at,
829
+ }));
830
+ }
831
+ updateProposalStatus(id, status, reason) {
832
+ this.db
833
+ .prepare(`UPDATE pattern_proposals SET status = ?, reviewed_at = datetime('now'), reviewer_reason = ? WHERE id = ?`)
834
+ .run(status, reason ?? null, id);
835
+ }
641
836
  close() {
642
837
  this.db.close();
643
838
  }
@@ -21,6 +21,7 @@ export interface ComplexityProfile {
21
21
  }
22
22
  export interface EpisodicRecord {
23
23
  id?: number;
24
+ issueId?: string;
24
25
  issueNumber?: number;
25
26
  prNumber?: number;
26
27
  pipelineType: string;
@@ -38,6 +39,8 @@ export interface EpisodicRecord {
38
39
  costUsd?: number;
39
40
  isRegression?: number;
40
41
  relatedEpisodes?: string;
42
+ priorityComposite?: number;
43
+ priorityConfidence?: number;
41
44
  }
42
45
  export interface AutonomyLedgerEntry {
43
46
  id?: number;
@@ -59,6 +62,7 @@ export type PipelineRunStatus = 'pending' | 'running' | 'completed' | 'failed' |
59
62
  export interface PipelineRun {
60
63
  id?: number;
61
64
  runId: string;
65
+ issueId?: string;
62
66
  issueNumber?: number;
63
67
  prNumber?: number;
64
68
  pipelineType: string;
@@ -95,6 +99,7 @@ export interface HotspotRecord {
95
99
  }
96
100
  export interface RoutingDecision {
97
101
  id?: number;
102
+ issueId?: string;
98
103
  issueNumber?: number;
99
104
  taskComplexity: number;
100
105
  codebaseComplexity: number;
@@ -113,6 +118,7 @@ export interface CostLedgerEntry {
113
118
  outputTokens?: number;
114
119
  totalTokens?: number;
115
120
  costUsd?: number;
121
+ issueId?: string;
116
122
  issueNumber?: number;
117
123
  prNumber?: number;
118
124
  stageName?: string;
@@ -150,6 +156,17 @@ export interface HandoffEvent {
150
156
  errorMessage?: string;
151
157
  createdAt?: string;
152
158
  }
159
+ export interface PriorityCalibrationSample {
160
+ id?: number;
161
+ issueId: string;
162
+ priorityComposite: number;
163
+ priorityConfidence: number;
164
+ priorityDimensions?: string;
165
+ actualComplexity?: number;
166
+ filesChanged?: number;
167
+ outcome?: string;
168
+ sampledAt?: string;
169
+ }
153
170
  export type DeploymentRecordState = 'pending' | 'deploying' | 'healthy' | 'unhealthy' | 'rolled-back' | 'failed';
154
171
  export interface DeploymentRecord {
155
172
  id?: number;
@@ -187,4 +204,39 @@ export interface AuditEntryRecord {
187
204
  signature?: string;
188
205
  createdAt?: string;
189
206
  }
207
+ export interface ToolSequenceEvent {
208
+ id?: number;
209
+ sessionId: string;
210
+ toolName: string;
211
+ actionCanonical: string;
212
+ projectPath?: string;
213
+ timestamp: string;
214
+ ingestedAt?: string;
215
+ }
216
+ export interface WorkflowPattern {
217
+ id?: number;
218
+ patternHash: string;
219
+ patternType: string;
220
+ sequenceJson: string;
221
+ frequency: number;
222
+ sessionCount: number;
223
+ confidence: number;
224
+ firstSeen?: string;
225
+ lastSeen?: string;
226
+ status: string;
227
+ detectedAt?: string;
228
+ }
229
+ export interface PatternProposal {
230
+ id?: number;
231
+ patternId: number;
232
+ proposalType: string;
233
+ artifactType: string;
234
+ artifactPath?: string;
235
+ draftContent: string;
236
+ confidence: number;
237
+ status: string;
238
+ reviewedAt?: string;
239
+ reviewerReason?: string;
240
+ createdAt?: string;
241
+ }
190
242
  //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Security triage pipeline — lightweight entry point that fetches an issue,
3
+ * runs the SecurityTriageRunner, posts findings as a comment, and applies
4
+ * a `rejected` or `triage-passed` label.
5
+ *
6
+ * **Asymmetric by design**: the triage agent can auto-reject issues above the
7
+ * risk threshold, but NEVER auto-approves (no `ai-ready` label). A human
8
+ * must review the triage analysis and manually apply `ai-ready`.
9
+ */
10
+ import type { IssueTracker } from '@ai-sdlc/reference';
11
+ import { type SecurityTriageConfig, type TriageVerdict } from './runners/security-triage.js';
12
+ import { type Logger } from './logger.js';
13
+ export interface TriageOptions {
14
+ /** Override the issue tracker (skips config-driven resolution). */
15
+ tracker?: IssueTracker;
16
+ /** SecurityTriageRunner configuration overrides. */
17
+ triageConfig?: SecurityTriageConfig;
18
+ /** Custom logger. */
19
+ logger?: Logger;
20
+ /** Working directory for config loading. Defaults to cwd. */
21
+ workDir?: string;
22
+ /** If true, skip posting a comment to the issue. */
23
+ dryRun?: boolean;
24
+ }
25
+ export interface TriageResult {
26
+ issueId: string;
27
+ verdict: TriageVerdict;
28
+ /** Whether the issue was auto-rejected (riskScore >= threshold). */
29
+ rejected: boolean;
30
+ /** The label applied to the issue, if any. */
31
+ labelApplied?: string;
32
+ /** Error message if the triage pipeline failed. */
33
+ error?: string;
34
+ }
35
+ export declare function executeTriage(issueId: string, options?: TriageOptions): Promise<TriageResult>;
36
+ //# sourceMappingURL=triage.d.ts.map