@ai-sdlc/orchestrator 0.5.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.
- package/dist/action-enforcement.d.ts +26 -0
- package/dist/action-enforcement.js +70 -0
- package/dist/admission-score.d.ts +58 -0
- package/dist/admission-score.js +164 -0
- package/dist/cycle-utils.d.ts +51 -0
- package/dist/cycle-utils.js +77 -0
- package/dist/defaults.d.ts +5 -0
- package/dist/defaults.js +5 -0
- package/dist/execute.js +121 -26
- package/dist/fix-ci.js +32 -2
- package/dist/fix-review.d.ts +66 -0
- package/dist/fix-review.js +441 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +13 -1
- package/dist/pipeline-cycle-detector.d.ts +70 -0
- package/dist/pipeline-cycle-detector.js +111 -0
- package/dist/priority.d.ts +2 -76
- package/dist/review.d.ts +31 -0
- package/dist/review.js +74 -0
- package/dist/runners/claude-code.js +314 -32
- package/dist/runners/index.d.ts +2 -1
- package/dist/runners/index.js +1 -0
- package/dist/runners/review-agent.d.ts +47 -0
- package/dist/runners/review-agent.js +220 -0
- package/dist/runners/security-triage.js +4 -0
- package/dist/runners/types.d.ts +19 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/schema.d.ts +2 -1
- package/dist/state/schema.js +54 -1
- package/dist/state/store.d.ts +17 -1
- package/dist/state/store.js +122 -0
- package/dist/state/types.d.ts +35 -0
- package/dist/workflow-patterns/artifact-writer.d.ts +16 -0
- package/dist/workflow-patterns/artifact-writer.js +34 -0
- package/dist/workflow-patterns/classifiers.d.ts +10 -0
- package/dist/workflow-patterns/classifiers.js +72 -0
- package/dist/workflow-patterns/detector.d.ts +27 -0
- package/dist/workflow-patterns/detector.js +186 -0
- package/dist/workflow-patterns/index.d.ts +8 -0
- package/dist/workflow-patterns/index.js +7 -0
- package/dist/workflow-patterns/proposal-generator.d.ts +15 -0
- package/dist/workflow-patterns/proposal-generator.js +183 -0
- package/dist/workflow-patterns/telemetry-ingest.d.ts +27 -0
- package/dist/workflow-patterns/telemetry-ingest.js +103 -0
- package/dist/workflow-patterns/types.d.ts +61 -0
- package/dist/workflow-patterns/types.js +11 -0
- package/package.json +2 -2
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR Review agent runner — analyzes pull request diffs for testing coverage,
|
|
3
|
+
* code quality, and security issues. Read-only: never modifies files.
|
|
4
|
+
*
|
|
5
|
+
* Uses the Anthropic Messages API directly (not Claude Code CLI)
|
|
6
|
+
* to produce a structured review verdict. Follows the SecurityTriageRunner
|
|
7
|
+
* pattern exactly.
|
|
8
|
+
*/
|
|
9
|
+
import { DEFAULT_ANTHROPIC_API_URL, DEFAULT_ANTHROPIC_MODEL, DEFAULT_LLM_TIMEOUT_MS, } from '../defaults.js';
|
|
10
|
+
// ── System prompts ───────────────────────────────────────────────────
|
|
11
|
+
const REVIEW_PROMPTS = {
|
|
12
|
+
testing: `You are a testing review agent analyzing a pull request diff. Your job is to verify that the changes are well-tested and that acceptance criteria are met.
|
|
13
|
+
|
|
14
|
+
Analyze the diff and any provided acceptance criteria. Check for:
|
|
15
|
+
1. **Test coverage**: Are new/changed code paths covered by tests?
|
|
16
|
+
2. **Acceptance criteria**: If provided, are all acceptance criteria addressed?
|
|
17
|
+
3. **Edge cases**: Are boundary conditions and error paths tested?
|
|
18
|
+
4. **Test quality**: Are tests meaningful (not just asserting true)?
|
|
19
|
+
5. **Missing tests**: Are there obvious test gaps for the changed code?
|
|
20
|
+
|
|
21
|
+
Respond with ONLY a JSON object (no markdown, no code fences):
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
"approved": true/false,
|
|
25
|
+
"findings": [
|
|
26
|
+
{"severity": "critical|major|minor|suggestion", "file": "path/to/file.ts", "line": 42, "message": "description"}
|
|
27
|
+
],
|
|
28
|
+
"summary": "1-2 sentence overall assessment"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Severity guide:
|
|
32
|
+
- critical: Missing tests for critical paths, acceptance criteria not met
|
|
33
|
+
- major: Significant test gaps for changed code
|
|
34
|
+
- minor: Minor test improvements possible
|
|
35
|
+
- suggestion: Nice-to-have test additions`,
|
|
36
|
+
critic: `You are a code quality review agent analyzing a pull request diff. Your job is to identify code quality issues, logic errors, and design problems.
|
|
37
|
+
|
|
38
|
+
Analyze the diff for:
|
|
39
|
+
1. **Logic errors**: Incorrect conditions, off-by-one errors, race conditions
|
|
40
|
+
2. **Code quality**: Naming, readability, unnecessary complexity
|
|
41
|
+
3. **Error handling**: Missing error cases, swallowed exceptions
|
|
42
|
+
4. **Design patterns**: Violations of existing project patterns/conventions
|
|
43
|
+
5. **Performance**: Obvious inefficiencies (N+1 queries, unnecessary allocations)
|
|
44
|
+
|
|
45
|
+
Do NOT flag style-only issues (formatting, trailing whitespace). Focus on substantive issues.
|
|
46
|
+
|
|
47
|
+
Respond with ONLY a JSON object (no markdown, no code fences):
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
"approved": true/false,
|
|
51
|
+
"findings": [
|
|
52
|
+
{"severity": "critical|major|minor|suggestion", "file": "path/to/file.ts", "line": 42, "message": "description"}
|
|
53
|
+
],
|
|
54
|
+
"summary": "1-2 sentence overall assessment"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Severity guide:
|
|
58
|
+
- critical: Logic errors, data loss risks, broken functionality
|
|
59
|
+
- major: Significant quality issues that should be fixed before merge
|
|
60
|
+
- minor: Improvements that would make the code better
|
|
61
|
+
- suggestion: Optional enhancements`,
|
|
62
|
+
security: `You are a security review agent analyzing a pull request diff. Your job is to identify security vulnerabilities in the changed code.
|
|
63
|
+
|
|
64
|
+
Analyze the diff for:
|
|
65
|
+
1. **Injection vulnerabilities**: SQL injection, command injection, XSS, template injection
|
|
66
|
+
2. **Authentication/authorization**: Missing auth checks, privilege escalation
|
|
67
|
+
3. **Credential exposure**: Hardcoded secrets, API keys, tokens in code
|
|
68
|
+
4. **Path traversal**: Unsanitized file paths, directory traversal
|
|
69
|
+
5. **Unsafe deserialization**: JSON.parse on untrusted input without validation
|
|
70
|
+
6. **Dependency issues**: Known vulnerable patterns, unsafe API usage
|
|
71
|
+
7. **Information disclosure**: Verbose error messages, stack traces in responses
|
|
72
|
+
|
|
73
|
+
Respond with ONLY a JSON object (no markdown, no code fences):
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
"approved": true/false,
|
|
77
|
+
"findings": [
|
|
78
|
+
{"severity": "critical|major|minor|suggestion", "file": "path/to/file.ts", "line": 42, "message": "description"}
|
|
79
|
+
],
|
|
80
|
+
"summary": "1-2 sentence overall assessment"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Severity guide:
|
|
84
|
+
- critical: Exploitable vulnerability (injection, credential leak, auth bypass)
|
|
85
|
+
- major: Security weakness that should be fixed (missing validation, unsafe patterns)
|
|
86
|
+
- minor: Defense-in-depth improvement
|
|
87
|
+
- suggestion: Security hardening opportunity`,
|
|
88
|
+
};
|
|
89
|
+
// ── Runner ───────────────────────────────────────────────────────────
|
|
90
|
+
export class ReviewAgentRunner {
|
|
91
|
+
config;
|
|
92
|
+
constructor(config) {
|
|
93
|
+
this.config = config;
|
|
94
|
+
}
|
|
95
|
+
get reviewType() {
|
|
96
|
+
return this.config.reviewType;
|
|
97
|
+
}
|
|
98
|
+
async run(ctx) {
|
|
99
|
+
const apiKey = this.config.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
100
|
+
if (!apiKey) {
|
|
101
|
+
return {
|
|
102
|
+
success: false,
|
|
103
|
+
filesChanged: [],
|
|
104
|
+
summary: 'Missing ANTHROPIC_API_KEY for PR review',
|
|
105
|
+
error: 'ANTHROPIC_API_KEY environment variable is not set',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const userContent = [
|
|
109
|
+
`## Pull Request Diff to Review`,
|
|
110
|
+
'',
|
|
111
|
+
ctx.issueBody, // diff is passed via issueBody
|
|
112
|
+
'',
|
|
113
|
+
...(ctx.ciErrors ? [`## Acceptance Criteria`, '', ctx.ciErrors, ''] : []),
|
|
114
|
+
`## Context`,
|
|
115
|
+
'',
|
|
116
|
+
`**Issue Title:** ${ctx.issueTitle}`,
|
|
117
|
+
].join('\n');
|
|
118
|
+
try {
|
|
119
|
+
const verdict = await this.callAPI(apiKey, userContent);
|
|
120
|
+
return {
|
|
121
|
+
success: true,
|
|
122
|
+
filesChanged: [], // Read-only — never modifies files
|
|
123
|
+
summary: JSON.stringify(verdict),
|
|
124
|
+
tokenUsage: verdict._tokenUsage,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
filesChanged: [],
|
|
131
|
+
summary: 'PR review failed',
|
|
132
|
+
error: err instanceof Error ? err.message : String(err),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async callAPI(apiKey, userContent) {
|
|
137
|
+
const apiUrl = this.config.apiUrl ?? DEFAULT_ANTHROPIC_API_URL;
|
|
138
|
+
const model = this.config.model ?? DEFAULT_ANTHROPIC_MODEL;
|
|
139
|
+
const timeoutMs = this.config.timeoutMs ?? DEFAULT_LLM_TIMEOUT_MS;
|
|
140
|
+
const controller = new AbortController();
|
|
141
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
142
|
+
try {
|
|
143
|
+
const res = await fetch(apiUrl, {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
headers: {
|
|
146
|
+
'Content-Type': 'application/json',
|
|
147
|
+
'x-api-key': apiKey,
|
|
148
|
+
'anthropic-version': '2023-06-01',
|
|
149
|
+
},
|
|
150
|
+
body: JSON.stringify({
|
|
151
|
+
model,
|
|
152
|
+
max_tokens: 4096,
|
|
153
|
+
system: this.config.reviewPolicy
|
|
154
|
+
? `${this.config.reviewPolicy}\n\n---\n\n${REVIEW_PROMPTS[this.config.reviewType]}`
|
|
155
|
+
: REVIEW_PROMPTS[this.config.reviewType],
|
|
156
|
+
messages: [{ role: 'user', content: userContent }],
|
|
157
|
+
}),
|
|
158
|
+
signal: controller.signal,
|
|
159
|
+
});
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
const text = await res.text().catch(() => '');
|
|
162
|
+
throw new Error(`Anthropic API error ${res.status}: ${text.slice(0, 200)}`);
|
|
163
|
+
}
|
|
164
|
+
const body = (await res.json());
|
|
165
|
+
const text = body.content?.[0]?.text ?? '';
|
|
166
|
+
const verdict = this.parseVerdict(text);
|
|
167
|
+
const tokenUsage = body.usage
|
|
168
|
+
? {
|
|
169
|
+
inputTokens: body.usage.input_tokens,
|
|
170
|
+
outputTokens: body.usage.output_tokens,
|
|
171
|
+
model: body.model ?? model,
|
|
172
|
+
}
|
|
173
|
+
: undefined;
|
|
174
|
+
return { ...verdict, _tokenUsage: tokenUsage };
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
clearTimeout(timeout);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
parseVerdict(text) {
|
|
181
|
+
// Strip markdown fences if the model wraps the JSON
|
|
182
|
+
const cleaned = text.replace(/^```(?:json)?\s*/m, '').replace(/\s*```$/m, '');
|
|
183
|
+
try {
|
|
184
|
+
const parsed = JSON.parse(cleaned);
|
|
185
|
+
const findings = Array.isArray(parsed.findings)
|
|
186
|
+
? parsed.findings.map((f) => ({
|
|
187
|
+
severity: ['critical', 'major', 'minor', 'suggestion'].includes(String(f.severity))
|
|
188
|
+
? String(f.severity)
|
|
189
|
+
: 'minor',
|
|
190
|
+
file: f.file ? String(f.file) : undefined,
|
|
191
|
+
line: typeof f.line === 'number' ? f.line : undefined,
|
|
192
|
+
message: String(f.message ?? ''),
|
|
193
|
+
}))
|
|
194
|
+
: [];
|
|
195
|
+
return {
|
|
196
|
+
type: this.config.reviewType,
|
|
197
|
+
approved: Boolean(parsed.approved),
|
|
198
|
+
findings,
|
|
199
|
+
summary: String(parsed.summary ?? ''),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// If JSON parse fails, treat as not approved — conservative
|
|
204
|
+
return {
|
|
205
|
+
type: this.config.reviewType,
|
|
206
|
+
approved: false,
|
|
207
|
+
findings: [
|
|
208
|
+
{
|
|
209
|
+
severity: 'critical',
|
|
210
|
+
message: 'Failed to parse review verdict — treating as not approved',
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
summary: `Review agent response was not valid JSON: ${text.slice(0, 200)}`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// ── Exported prompts for testing ─────────────────────────────────────
|
|
219
|
+
export { REVIEW_PROMPTS };
|
|
220
|
+
//# sourceMappingURL=review-agent.js.map
|
|
@@ -54,6 +54,10 @@ export class SecurityTriageRunner {
|
|
|
54
54
|
error: 'ANTHROPIC_API_KEY environment variable is not set',
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
+
// Warn if issue body is empty or whitespace-only
|
|
58
|
+
if (!ctx.issueBody || ctx.issueBody.trim() === '') {
|
|
59
|
+
console.warn(`[SecurityTriageRunner] Warning: Issue #${ctx.issueId} has an empty body. Triage quality may be degraded.`);
|
|
60
|
+
}
|
|
57
61
|
const userContent = [
|
|
58
62
|
`## Issue to Analyze`,
|
|
59
63
|
'',
|
package/dist/runners/types.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface AgentContext {
|
|
|
20
20
|
};
|
|
21
21
|
/** CI failure logs, populated only during fix-CI retries. */
|
|
22
22
|
ciErrors?: string;
|
|
23
|
+
/** Review findings from PR reviews, populated only during fix-review retries. */
|
|
24
|
+
reviewFindings?: string;
|
|
23
25
|
/** Agent memory for long-term/episodic recall. */
|
|
24
26
|
memory?: AgentMemory;
|
|
25
27
|
/** Override the default tool allowlist for the agent subprocess. */
|
|
@@ -36,12 +38,29 @@ export interface AgentContext {
|
|
|
36
38
|
lintCommand?: string;
|
|
37
39
|
/** Format command for agent prompt (e.g., `npm run format`). */
|
|
38
40
|
formatCommand?: string;
|
|
41
|
+
/** Typecheck command for agent prompt (e.g., `pnpm build`). */
|
|
42
|
+
typecheckCommand?: string;
|
|
39
43
|
/** Commit message template with `{issueNumber}` and `{issueTitle}` placeholders. */
|
|
40
44
|
commitMessageTemplate?: string;
|
|
41
45
|
/** Co-author line for commits. */
|
|
42
46
|
commitCoAuthor?: string;
|
|
43
47
|
/** OpenShell sandbox ID — when set, the runner spawns the agent inside this sandbox. */
|
|
44
48
|
sandboxId?: string;
|
|
49
|
+
/** Progress callback — called with streaming events as the agent works. */
|
|
50
|
+
onProgress?: (event: AgentProgressEvent) => void;
|
|
51
|
+
}
|
|
52
|
+
/** Streaming progress event emitted by the agent runner. */
|
|
53
|
+
export interface AgentProgressEvent {
|
|
54
|
+
/** Event type. */
|
|
55
|
+
type: 'tool_start' | 'tool_end' | 'text' | 'error' | 'cost';
|
|
56
|
+
/** Tool name (for tool_start/tool_end events). */
|
|
57
|
+
tool?: string;
|
|
58
|
+
/** File path or resource being acted on. */
|
|
59
|
+
file?: string;
|
|
60
|
+
/** Short description of what's happening. */
|
|
61
|
+
message?: string;
|
|
62
|
+
/** Cost in USD so far (for cost events). */
|
|
63
|
+
costUsd?: number;
|
|
45
64
|
}
|
|
46
65
|
export interface TokenUsage {
|
|
47
66
|
inputTokens: number;
|
package/dist/state/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { StateStore } from './store.js';
|
|
2
2
|
export { CURRENT_SCHEMA_VERSION, SCHEMA_DDL, MIGRATION_V2, MIGRATION_V3, MIGRATION_V4, MIGRATION_V5, MIGRATIONS, } from './schema.js';
|
|
3
|
-
export type { ComplexityProfile, EpisodicRecord, AutonomyLedgerEntry, PipelineRun, PipelineRunStatus, Convention, HotspotRecord, RoutingDecision, CostLedgerEntry, GateThresholdOverride, AutonomyEvent, AutonomyEventType, HandoffEvent, DeploymentRecord, DeploymentRecordState, RolloutStepRecord, AuditEntryRecord, PriorityCalibrationSample, } from './types.js';
|
|
3
|
+
export type { ComplexityProfile, EpisodicRecord, AutonomyLedgerEntry, PipelineRun, PipelineRunStatus, Convention, HotspotRecord, RoutingDecision, CostLedgerEntry, GateThresholdOverride, AutonomyEvent, AutonomyEventType, HandoffEvent, DeploymentRecord, DeploymentRecordState, RolloutStepRecord, AuditEntryRecord, PriorityCalibrationSample, ToolSequenceEvent, WorkflowPattern, PatternProposal, } from './types.js';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/state/schema.d.ts
CHANGED
|
@@ -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 =
|
|
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;
|
|
@@ -14,5 +14,6 @@ export declare const MIGRATION_V5 = "\n-- Deployment records\nCREATE TABLE IF NO
|
|
|
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
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
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";
|
|
17
18
|
export declare const MIGRATIONS: Migration[];
|
|
18
19
|
//# sourceMappingURL=schema.d.ts.map
|
package/dist/state/schema.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SQLite DDL and migrations for the state store.
|
|
3
3
|
*/
|
|
4
|
-
export const CURRENT_SCHEMA_VERSION =
|
|
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,
|
|
@@ -262,6 +262,55 @@ CREATE INDEX IF NOT EXISTS idx_priority_calibration_sampled ON priority_calibrat
|
|
|
262
262
|
ALTER TABLE episodic_memory ADD COLUMN priority_composite REAL;
|
|
263
263
|
ALTER TABLE episodic_memory ADD COLUMN priority_confidence REAL;
|
|
264
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
|
+
`;
|
|
265
314
|
export const MIGRATIONS = [
|
|
266
315
|
{
|
|
267
316
|
version: 1,
|
|
@@ -295,5 +344,9 @@ export const MIGRATIONS = [
|
|
|
295
344
|
version: 8,
|
|
296
345
|
sql: MIGRATION_V8,
|
|
297
346
|
},
|
|
347
|
+
{
|
|
348
|
+
version: 9,
|
|
349
|
+
sql: MIGRATION_V9,
|
|
350
|
+
},
|
|
298
351
|
];
|
|
299
352
|
//# sourceMappingURL=schema.js.map
|
package/dist/state/store.d.ts
CHANGED
|
@@ -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, PriorityCalibrationSample } 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);
|
|
@@ -118,6 +118,22 @@ export declare class StateStore {
|
|
|
118
118
|
}): number;
|
|
119
119
|
/** Expose the underlying database for direct queries (e.g. dashboard). */
|
|
120
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;
|
|
121
137
|
close(): void;
|
|
122
138
|
}
|
|
123
139
|
//# sourceMappingURL=store.d.ts.map
|
package/dist/state/store.js
CHANGED
|
@@ -711,6 +711,128 @@ export class StateStore {
|
|
|
711
711
|
getDatabase() {
|
|
712
712
|
return this.db;
|
|
713
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
|
+
}
|
|
714
836
|
close() {
|
|
715
837
|
this.db.close();
|
|
716
838
|
}
|
package/dist/state/types.d.ts
CHANGED
|
@@ -204,4 +204,39 @@ export interface AuditEntryRecord {
|
|
|
204
204
|
signature?: string;
|
|
205
205
|
createdAt?: string;
|
|
206
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
|
+
}
|
|
207
242
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact writer — generates files for approved pattern proposals.
|
|
3
|
+
* Never overwrites existing files.
|
|
4
|
+
*/
|
|
5
|
+
export interface WriteResult {
|
|
6
|
+
success: boolean;
|
|
7
|
+
filePath: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Write a proposal artifact to the filesystem.
|
|
12
|
+
* Creates parent directories if needed.
|
|
13
|
+
* Returns error if file already exists (never overwrites).
|
|
14
|
+
*/
|
|
15
|
+
export declare function writeArtifact(projectDir: string, relativePath: string, content: string): WriteResult;
|
|
16
|
+
//# sourceMappingURL=artifact-writer.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact writer — generates files for approved pattern proposals.
|
|
3
|
+
* Never overwrites existing files.
|
|
4
|
+
*/
|
|
5
|
+
import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
6
|
+
import { join, dirname } from 'node:path';
|
|
7
|
+
/**
|
|
8
|
+
* Write a proposal artifact to the filesystem.
|
|
9
|
+
* Creates parent directories if needed.
|
|
10
|
+
* Returns error if file already exists (never overwrites).
|
|
11
|
+
*/
|
|
12
|
+
export function writeArtifact(projectDir, relativePath, content) {
|
|
13
|
+
const fullPath = join(projectDir, relativePath);
|
|
14
|
+
if (existsSync(fullPath)) {
|
|
15
|
+
return {
|
|
16
|
+
success: false,
|
|
17
|
+
filePath: fullPath,
|
|
18
|
+
error: `File already exists: ${relativePath}`,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
mkdirSync(dirname(fullPath), { recursive: true });
|
|
23
|
+
writeFileSync(fullPath, content, 'utf-8');
|
|
24
|
+
return { success: true, filePath: fullPath };
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return {
|
|
28
|
+
success: false,
|
|
29
|
+
filePath: fullPath,
|
|
30
|
+
error: err instanceof Error ? err.message : String(err),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=artifact-writer.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern type classifiers — refine detected patterns into
|
|
3
|
+
* specific automation types based on their structure.
|
|
4
|
+
*/
|
|
5
|
+
import type { DetectedPattern } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Classify a detected pattern into a specific type and suggest an artifact.
|
|
8
|
+
*/
|
|
9
|
+
export declare function classifyPattern(pattern: DetectedPattern): DetectedPattern;
|
|
10
|
+
//# sourceMappingURL=classifiers.d.ts.map
|