@event4u/agent-config 8.4.0 → 8.4.1
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +1 -1
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +1 -1
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +1 -1
- package/dist/mcp/registry-manifest.json +1 -1
- package/package.json +4 -1
- package/src/agent-src/README.md +64 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +497 -0
- package/src/agent-src/scripts/scan-seeder-violations.php +145 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +1087 -0
- package/src/agent-src/templates/AGENTS.md +34 -0
- package/src/agent-src/templates/scripts/README.md +195 -0
- package/src/agent-src/templates/scripts/check_memory.ts +662 -0
- package/src/agent-src/templates/scripts/check_memory_proposal.ts +370 -0
- package/src/agent-src/templates/scripts/historical-bug-patterns.example.yml +84 -0
- package/src/agent-src/templates/scripts/implement_ticket/__main__.ts +27 -0
- package/src/agent-src/templates/scripts/memory_hash.ts +355 -0
- package/src/agent-src/templates/scripts/memory_lookup.ts +1089 -0
- package/src/agent-src/templates/scripts/memory_report.ts +868 -0
- package/src/agent-src/templates/scripts/memory_signal.ts +444 -0
- package/src/agent-src/templates/scripts/memory_status.ts +215 -0
- package/src/agent-src/templates/scripts/ownership-map.example.yml +87 -0
- package/src/agent-src/templates/scripts/pr-risk-config.example.yml +76 -0
- package/src/agent-src/templates/scripts/pr_review_routing.ts +544 -0
- package/src/agent-src/templates/scripts/pr_risk_review.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/boundary.ts +164 -0
- package/src/agent-src/templates/scripts/telemetry/engagement.ts +479 -0
- package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +394 -0
- package/src/agent-src/templates/scripts/telemetry/settings.ts +210 -0
- package/src/agent-src/templates/scripts/telemetry_record.ts +276 -0
- package/src/agent-src/templates/scripts/telemetry_report.ts +211 -0
- package/src/agent-src/templates/scripts/telemetry_status.ts +333 -0
- package/src/agent-src/templates/scripts/tier_usage_report.ts +619 -0
- package/src/agent-src/templates/scripts/work_engine/__main__.ts +14 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +1261 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/user_global_paths.ts +329 -0
- package/src/agent-src/templates/scripts/work_engine/cli.ts +206 -0
- package/src/agent-src/templates/scripts/work_engine/cli_args.ts +249 -0
- package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +225 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/analyze.ts +125 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +189 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +94 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/memory.ts +193 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/plan.ts +267 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +518 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/report.ts +379 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +268 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +258 -0
- package/src/agent-src/templates/scripts/work_engine/directives/index.ts +32 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/contract.ts +243 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/index.ts +108 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +259 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/ui.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/_passthrough.ts +40 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +241 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +506 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +325 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/index.ts +102 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +462 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +474 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +352 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.ts +33 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.ts +213 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/index.ts +111 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.ts +126 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/report.ts +112 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/test.ts +164 -0
- package/src/agent-src/templates/scripts/work_engine/dispatcher.ts +515 -0
- package/src/agent-src/templates/scripts/work_engine/emitters.ts +119 -0
- package/src/agent-src/templates/scripts/work_engine/errors.ts +24 -0
- package/src/agent-src/templates/scripts/work_engine/hook_bootstrap.ts +104 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.ts +189 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.ts +41 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.ts +89 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +193 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +304 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.ts +110 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.ts +118 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/index.ts +17 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +161 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.ts +45 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/trace.ts +134 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/context.ts +94 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/events.ts +58 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/exceptions.ts +85 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/index.ts +27 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/registry.ts +66 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/runner.ts +90 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/settings.ts +260 -0
- package/src/agent-src/templates/scripts/work_engine/input_builders.ts +260 -0
- package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +466 -0
- package/src/agent-src/templates/scripts/work_engine/migration/v0_to_v1.ts +531 -0
- package/src/agent-src/templates/scripts/work_engine/orchestration.ts +366 -0
- package/src/agent-src/templates/scripts/work_engine/persona_policy.ts +97 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/diff.ts +135 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/file.ts +175 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/prompt.ts +115 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/confidence.ts +415 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +466 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/decision_trace.ts +298 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +444 -0
- package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +252 -0
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +745 -0
- package/src/agent-src/templates/scripts/work_engine/state.ts +1151 -0
- package/src/agent-src/templates/scripts/work_engine/state_io.ts +413 -0
- package/src/install/atomic.ts +111 -0
- package/src/install/conflict.ts +226 -0
- package/src/install/detect.ts +280 -0
- package/src/install/paths.ts +90 -0
- package/src/install/plan.ts +205 -0
- package/src/install/preflight.ts +210 -0
- package/src/install/selectedTools.ts +52 -0
- package/src/install/toolDetection.ts +120 -0
- package/src/install/txlog.ts +161 -0
- package/src/install/types.ts +175 -0
- package/src/install/wizard-plan.ts +216 -0
- package/src/scripts/prepack-check.mjs +108 -5
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Gate script for memory promotion (intake → curated).
|
|
4
|
+
*
|
|
5
|
+
* TypeScript twin of `src/agent-src/templates/scripts/check_memory_proposal.py`
|
|
6
|
+
* (ADR-200, Phase 1 — consumer-shipped template). Adapted from the dev-side
|
|
7
|
+
* `src/scripts/check_memory_proposal.ts` twin: the template `.py` is identical
|
|
8
|
+
* EXCEPT it lacks the dev-side `--quiet` flag (gate-passed line always prints),
|
|
9
|
+
* so this twin drops `--quiet` to match the template surface exactly.
|
|
10
|
+
* Mirrors the Python CLI contract EXACTLY — mutually
|
|
11
|
+
* exclusive `--intake-id` / `--proposal` (one required), `--format`
|
|
12
|
+
* (text|json), exit codes (0 pass, 1 gate failure / not-found,
|
|
13
|
+
* 2 PyYAML missing — N/A here, 3 internal), stdout/stderr split,
|
|
14
|
+
* byte-identical messages (incl. Python set-repr of VALID_TYPES), same
|
|
15
|
+
* intake scan + pattern logic. No behaviour changes.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import * as fs from 'node:fs';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
21
|
+
import YAML from 'yaml';
|
|
22
|
+
|
|
23
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
24
|
+
|
|
25
|
+
// Relative to cwd, mirroring Python `Path("agents/memory/intake")`.
|
|
26
|
+
// Injectable for tests (monkeypatch parity).
|
|
27
|
+
let INTAKE_ROOT = path.join('agents', 'memory', 'intake');
|
|
28
|
+
|
|
29
|
+
const VALID_TYPES: ReadonlySet<string> = new Set([
|
|
30
|
+
'historical-patterns',
|
|
31
|
+
'incident-learnings',
|
|
32
|
+
'ownership',
|
|
33
|
+
'domain-invariants',
|
|
34
|
+
'product-rules',
|
|
35
|
+
]);
|
|
36
|
+
const REQUIRED_INTAKE = ['id', 'entry_type', 'path', 'body'] as const;
|
|
37
|
+
const PATTERN_MIN_PATHS = 2;
|
|
38
|
+
const MIN_FUTURE_DECISIONS = 3;
|
|
39
|
+
|
|
40
|
+
type Record_ = Record<string, unknown>;
|
|
41
|
+
|
|
42
|
+
/** Mirror Python `repr(sorted(set))` for a string set. */
|
|
43
|
+
function _pyList(items: readonly string[]): string {
|
|
44
|
+
const sorted = items.slice().sort();
|
|
45
|
+
return '[' + sorted.map((s) => `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`).join(', ') + ']';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function _isDir(p: string): boolean {
|
|
49
|
+
try {
|
|
50
|
+
return fs.statSync(p).isDirectory();
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Mirror sorted(glob("*.jsonl")) on INTAKE_ROOT — full paths, sorted. */
|
|
57
|
+
function _jsonlFilesSorted(): string[] {
|
|
58
|
+
let entries: string[];
|
|
59
|
+
try {
|
|
60
|
+
entries = fs.readdirSync(INTAKE_ROOT);
|
|
61
|
+
} catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
return entries
|
|
65
|
+
.filter((e) => e.endsWith('.jsonl'))
|
|
66
|
+
.map((e) => path.join(INTAKE_ROOT, e))
|
|
67
|
+
.sort();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Unsorted glob (Python `.glob` without sorted) for _count_sibling_paths. */
|
|
71
|
+
function _jsonlFilesUnsorted(): string[] {
|
|
72
|
+
let entries: string[];
|
|
73
|
+
try {
|
|
74
|
+
entries = fs.readdirSync(INTAKE_ROOT);
|
|
75
|
+
} catch {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
return entries.filter((e) => e.endsWith('.jsonl')).map((e) => path.join(INTAKE_ROOT, e));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function _load_yaml(p: string): Record_ {
|
|
82
|
+
let data: unknown;
|
|
83
|
+
data = YAML.parse(fs.readFileSync(p, 'utf-8'), { version: '1.1' });
|
|
84
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
85
|
+
process.stderr.write(`error: ${p} is not a YAML mapping\n`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
return data as Record_;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function _find_intake(intake_id: string): Record_ | null {
|
|
92
|
+
if (!_isDir(INTAKE_ROOT)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
for (const jsonl of _jsonlFilesSorted()) {
|
|
96
|
+
const text = fs.readFileSync(jsonl, 'utf-8');
|
|
97
|
+
for (let line of text.split('\n')) {
|
|
98
|
+
line = line.trim();
|
|
99
|
+
if (line === '') {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
let obj: unknown;
|
|
103
|
+
try {
|
|
104
|
+
obj = JSON.parse(line);
|
|
105
|
+
} catch {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (obj !== null && typeof obj === 'object' && (obj as Record_)['id'] === intake_id) {
|
|
109
|
+
return obj as Record_;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function _count_sibling_paths(entry_type: unknown, body: unknown): number {
|
|
117
|
+
if (!_isDir(INTAKE_ROOT)) {
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
const seen = new Set<string>();
|
|
121
|
+
for (const jsonl of _jsonlFilesUnsorted()) {
|
|
122
|
+
const text = fs.readFileSync(jsonl, 'utf-8');
|
|
123
|
+
for (let line of text.split('\n')) {
|
|
124
|
+
line = line.trim();
|
|
125
|
+
if (line === '') {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
let obj: unknown;
|
|
129
|
+
try {
|
|
130
|
+
obj = JSON.parse(line);
|
|
131
|
+
} catch {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (obj === null || typeof obj !== 'object') {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const o = obj as Record_;
|
|
138
|
+
if (
|
|
139
|
+
o['entry_type'] === entry_type &&
|
|
140
|
+
o['body'] === body &&
|
|
141
|
+
typeof o['path'] === 'string'
|
|
142
|
+
) {
|
|
143
|
+
seen.add(o['path'] as string);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return seen.size;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function _check_future_decisions(fds: unknown): string[] {
|
|
151
|
+
const failures: string[] = [];
|
|
152
|
+
if (!Array.isArray(fds)) {
|
|
153
|
+
return ['future_decisions: missing or not a list'];
|
|
154
|
+
}
|
|
155
|
+
if (fds.length < MIN_FUTURE_DECISIONS) {
|
|
156
|
+
failures.push(
|
|
157
|
+
`future_decisions: needs ≥${MIN_FUTURE_DECISIONS}, got ${fds.length}`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
for (let idx = 0; idx < fds.length; idx++) {
|
|
161
|
+
const i = idx + 1;
|
|
162
|
+
const fd = fds[idx];
|
|
163
|
+
if (fd === null || typeof fd !== 'object' || Array.isArray(fd)) {
|
|
164
|
+
failures.push(`future_decisions[${i}]: must be a mapping`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const fdObj = fd as Record_;
|
|
168
|
+
for (const key of ['decision', 'expected_by', 'owner']) {
|
|
169
|
+
if (!fdObj[key]) {
|
|
170
|
+
failures.push(`future_decisions[${i}]: missing \`${key}\``);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return failures;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function check(record: Record_, _source: string): string[] {
|
|
178
|
+
const failures: string[] = [];
|
|
179
|
+
// 1. required fields
|
|
180
|
+
for (const key of REQUIRED_INTAKE) {
|
|
181
|
+
const v = record[key];
|
|
182
|
+
if (!(key in record) || v === null || v === undefined || v === '') {
|
|
183
|
+
failures.push(`missing field: \`${key}\``);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// 2. type value
|
|
187
|
+
const etype = record['entry_type'];
|
|
188
|
+
if (!VALID_TYPES.has(etype as string)) {
|
|
189
|
+
failures.push(
|
|
190
|
+
`entry_type \`${etype === undefined || etype === null ? 'None' : String(etype)}\` not in ${_pyList([...VALID_TYPES])}`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
// 3. pattern vs one-off
|
|
194
|
+
const body = record['body'];
|
|
195
|
+
const sibling_paths = body && etype ? _count_sibling_paths(etype, body) : 0;
|
|
196
|
+
const fds = record['future_decisions'];
|
|
197
|
+
if (sibling_paths >= PATTERN_MIN_PATHS) {
|
|
198
|
+
// strong pattern signal
|
|
199
|
+
} else {
|
|
200
|
+
const fd_errors = _check_future_decisions(fds);
|
|
201
|
+
if (fd_errors.length > 0) {
|
|
202
|
+
failures.push(
|
|
203
|
+
`weak pattern evidence (${sibling_paths} sibling path(s)) ` +
|
|
204
|
+
`and future_decisions insufficient:`,
|
|
205
|
+
);
|
|
206
|
+
for (const e of fd_errors) {
|
|
207
|
+
failures.push(` - ${e}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return failures;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// --- argparse port ---------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
interface ParsedArgs {
|
|
217
|
+
intake_id: string | null;
|
|
218
|
+
proposal: string | null;
|
|
219
|
+
format: 'text' | 'json';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function _argparse_error(message: string): never {
|
|
223
|
+
process.stderr.write(`check_memory_proposal: error: ${message}\n`);
|
|
224
|
+
process.exit(2);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function parse_args(argv: readonly string[]): ParsedArgs {
|
|
228
|
+
let intake_id: string | null = null;
|
|
229
|
+
let proposal: string | null = null;
|
|
230
|
+
let format: 'text' | 'json' = 'text';
|
|
231
|
+
// argparse reports a mutually-exclusive conflict against the SECOND-seen
|
|
232
|
+
// arg at the point it is parsed (order-sensitive), so check inline rather
|
|
233
|
+
// than after the loop.
|
|
234
|
+
for (let i = 0; i < argv.length; i++) {
|
|
235
|
+
const arg = argv[i]!;
|
|
236
|
+
if (arg === '--intake-id' || arg.startsWith('--intake-id=')) {
|
|
237
|
+
if (proposal !== null) {
|
|
238
|
+
_argparse_error('argument --intake-id: not allowed with argument --proposal');
|
|
239
|
+
}
|
|
240
|
+
intake_id = arg === '--intake-id' ? _need(argv, ++i, '--intake-id') : arg.slice('--intake-id='.length);
|
|
241
|
+
} else if (arg === '--proposal' || arg.startsWith('--proposal=')) {
|
|
242
|
+
if (intake_id !== null) {
|
|
243
|
+
_argparse_error('argument --proposal: not allowed with argument --intake-id');
|
|
244
|
+
}
|
|
245
|
+
proposal = arg === '--proposal' ? _need(argv, ++i, '--proposal') : arg.slice('--proposal='.length);
|
|
246
|
+
} else if (arg === '--format') {
|
|
247
|
+
const v = _need(argv, ++i, '--format');
|
|
248
|
+
format = _checkFormat(v);
|
|
249
|
+
} else if (arg.startsWith('--format=')) {
|
|
250
|
+
format = _checkFormat(arg.slice('--format='.length));
|
|
251
|
+
} else if (arg === '-h' || arg === '--help') {
|
|
252
|
+
process.stdout.write(
|
|
253
|
+
'usage: check_memory_proposal [-h] (--intake-id INTAKE_ID | --proposal PROPOSAL)\n' +
|
|
254
|
+
' [--format {text,json}]\n',
|
|
255
|
+
);
|
|
256
|
+
process.exit(0);
|
|
257
|
+
} else {
|
|
258
|
+
_argparse_error(`unrecognized arguments: ${arg}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (intake_id === null && proposal === null) {
|
|
262
|
+
_argparse_error('one of the arguments --intake-id --proposal is required');
|
|
263
|
+
}
|
|
264
|
+
return { intake_id, proposal, format };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function _need(argv: readonly string[], i: number, flag: string): string {
|
|
268
|
+
const v = argv[i];
|
|
269
|
+
if (v === undefined) {
|
|
270
|
+
_argparse_error(`argument ${flag}: expected one argument`);
|
|
271
|
+
}
|
|
272
|
+
return v;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function _checkFormat(v: string): 'text' | 'json' {
|
|
276
|
+
if (v !== 'text' && v !== 'json') {
|
|
277
|
+
_argparse_error(`argument --format: invalid choice: '${v}' (choose from 'text', 'json')`);
|
|
278
|
+
}
|
|
279
|
+
return v;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Mirror Python `json.dumps(obj, indent=2)` with ensure_ascii. */
|
|
283
|
+
function _json_dumps_ascii(obj: unknown): string {
|
|
284
|
+
const raw = JSON.stringify(obj, null, 2);
|
|
285
|
+
let out = '';
|
|
286
|
+
for (const ch of raw) {
|
|
287
|
+
const code = ch.codePointAt(0)!;
|
|
288
|
+
if (code < 0x80) {
|
|
289
|
+
out += ch;
|
|
290
|
+
} else {
|
|
291
|
+
for (let k = 0; k < ch.length; k++) {
|
|
292
|
+
out += '\\u' + ch.charCodeAt(k).toString(16).padStart(4, '0');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function main(argv?: readonly string[]): number {
|
|
300
|
+
const args = parse_args(argv ?? process.argv.slice(2));
|
|
301
|
+
let record: Record_;
|
|
302
|
+
let source: string;
|
|
303
|
+
if (args.intake_id !== null) {
|
|
304
|
+
const found = _find_intake(args.intake_id);
|
|
305
|
+
if (found === null) {
|
|
306
|
+
process.stderr.write(`error: no intake entry with id=${args.intake_id}\n`);
|
|
307
|
+
return 1;
|
|
308
|
+
}
|
|
309
|
+
record = found;
|
|
310
|
+
source = `intake:${args.intake_id}`;
|
|
311
|
+
} else {
|
|
312
|
+
record = _load_yaml(args.proposal!);
|
|
313
|
+
source = args.proposal!;
|
|
314
|
+
}
|
|
315
|
+
const failures = check(record, source);
|
|
316
|
+
if (args.format === 'json') {
|
|
317
|
+
process.stdout.write(_json_dumps_ascii({ source, failures }) + '\n');
|
|
318
|
+
} else {
|
|
319
|
+
if (failures.length > 0) {
|
|
320
|
+
process.stdout.write(`❌ ${source} — gate failed:\n`);
|
|
321
|
+
for (const f of failures) {
|
|
322
|
+
process.stdout.write(` 🔴 ${f}\n`);
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
process.stdout.write(`✅ ${source} — gate passed\n`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return failures.length > 0 ? 1 : 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function _set_intake_root_for_test(root: string): void {
|
|
332
|
+
INTAKE_ROOT = root;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function _isCliEntry(): boolean {
|
|
336
|
+
if (process.argv[1] === undefined) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
340
|
+
if (import.meta.url === argvUrl) {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
// A symlinked invocation (e.g. via an installed `.augment/` projection,
|
|
344
|
+
// or macOS /var → /private/var temp dirs) makes the raw URLs differ:
|
|
345
|
+
// import.meta.url is the resolved real path while argv[1] keeps the
|
|
346
|
+
// symlink path. Compare realpaths so the entry guard still fires
|
|
347
|
+
// (without this the CLI silently no-ops when run through a symlink).
|
|
348
|
+
try {
|
|
349
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
350
|
+
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
351
|
+
return here === argv;
|
|
352
|
+
} catch {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
358
|
+
process.exit(main());
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export {
|
|
362
|
+
VALID_TYPES,
|
|
363
|
+
REQUIRED_INTAKE,
|
|
364
|
+
_find_intake,
|
|
365
|
+
_count_sibling_paths,
|
|
366
|
+
_check_future_decisions,
|
|
367
|
+
check,
|
|
368
|
+
main,
|
|
369
|
+
_set_intake_root_for_test,
|
|
370
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Historical bug patterns — consumed by scripts/pr_review_routing.ts
|
|
2
|
+
#
|
|
3
|
+
# Copy this file to `.github/historical-bug-patterns.yml` (preferred) or
|
|
4
|
+
# `agents/historical-bug-patterns.yml` and curate the patterns from your
|
|
5
|
+
# project's actual postmortems. Every pattern must reference a real prior
|
|
6
|
+
# failure — this file is not a wishlist.
|
|
7
|
+
#
|
|
8
|
+
# A pattern matches when ANY changed file in the PR matches ANY of its
|
|
9
|
+
# `paths` globs. Routing severity is the max of matched pattern severities.
|
|
10
|
+
|
|
11
|
+
version: 1
|
|
12
|
+
updated: 2026-04-21
|
|
13
|
+
|
|
14
|
+
patterns:
|
|
15
|
+
- id: n-plus-one-tenant-listing
|
|
16
|
+
label: "N+1 on tenant-scoped listings"
|
|
17
|
+
severity: medium
|
|
18
|
+
paths:
|
|
19
|
+
- "app/Http/Controllers/**Listing*.php"
|
|
20
|
+
- "app/Http/Controllers/**Index*.php"
|
|
21
|
+
- "app/Services/**Query*.php"
|
|
22
|
+
required_test: "assert query count ≤ N for a multi-row tenant fixture"
|
|
23
|
+
references:
|
|
24
|
+
- "PR #842 — production slowdown 2024-09"
|
|
25
|
+
|
|
26
|
+
- id: missing-policy-on-admin-route
|
|
27
|
+
label: "Admin-only route without Gate::allows / policy"
|
|
28
|
+
severity: high
|
|
29
|
+
paths:
|
|
30
|
+
- "app/Http/Controllers/Admin/**"
|
|
31
|
+
- "routes/admin.php"
|
|
32
|
+
required_test: "negative test: non-admin receives 403"
|
|
33
|
+
references:
|
|
34
|
+
- "INC-214 — non-admin reached admin settings endpoint"
|
|
35
|
+
|
|
36
|
+
- id: queue-job-not-idempotent
|
|
37
|
+
label: "Queue job without idempotency key"
|
|
38
|
+
severity: high
|
|
39
|
+
paths:
|
|
40
|
+
- "app/Jobs/**"
|
|
41
|
+
- "src/MessageHandler/**"
|
|
42
|
+
required_test: "assert a retried job does not double-write"
|
|
43
|
+
|
|
44
|
+
- id: secret-in-config-commit
|
|
45
|
+
label: "Secret-looking value committed to config"
|
|
46
|
+
severity: high
|
|
47
|
+
paths:
|
|
48
|
+
- "**/config/**.php"
|
|
49
|
+
- "**/config/**.yaml"
|
|
50
|
+
- "**/config/**.yml"
|
|
51
|
+
required_test: "grep-based check in CI that the commit carries no API keys"
|
|
52
|
+
references:
|
|
53
|
+
- "INC-088 — API key leaked in config/services.php"
|
|
54
|
+
|
|
55
|
+
- id: missing-index-on-foreign-key
|
|
56
|
+
label: "New foreign key without covering index"
|
|
57
|
+
severity: medium
|
|
58
|
+
paths:
|
|
59
|
+
- "**/migrations/**"
|
|
60
|
+
required_test: "assert EXPLAIN on a representative query uses the index"
|
|
61
|
+
|
|
62
|
+
- id: blade-unsafe-html
|
|
63
|
+
label: "Blade component renders user input with {!! !!}"
|
|
64
|
+
severity: high
|
|
65
|
+
paths:
|
|
66
|
+
- "resources/views/**.blade.php"
|
|
67
|
+
required_test: "confirm the rendered field is escaped or sanitized upstream"
|
|
68
|
+
references:
|
|
69
|
+
- "CVE-2023-XXX-style XSS from unsanitized profile field"
|
|
70
|
+
|
|
71
|
+
- id: webhook-without-signature-check
|
|
72
|
+
label: "Webhook handler without signature verification"
|
|
73
|
+
severity: high
|
|
74
|
+
paths:
|
|
75
|
+
- "app/Http/Controllers/**Webhook*.php"
|
|
76
|
+
- "app/api/**webhook**"
|
|
77
|
+
required_test: "negative test: unsigned payload returns 401"
|
|
78
|
+
|
|
79
|
+
- id: migration-no-rollback
|
|
80
|
+
label: "Migration with destructive up() and empty down()"
|
|
81
|
+
severity: medium
|
|
82
|
+
paths:
|
|
83
|
+
- "**/migrations/**"
|
|
84
|
+
required_test: "document in the PR why rollback is intentionally not supported"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deprecated CLI entry point — delegates to `work_engine`.
|
|
3
|
+
*
|
|
4
|
+
* TypeScript twin of `implement_ticket/__main__.py` (ADR-200, Python→TypeScript
|
|
5
|
+
* migration). `python3 -m implement_ticket` still works because the
|
|
6
|
+
* Golden-Transcript freeze-guard pins that invocation; the twin mirrors it 1:1 —
|
|
7
|
+
* import `main`, run it, and set `process.exitCode` to the returned code, never
|
|
8
|
+
* `process.exit` (per ADR-200, so flushing + cleanup run as Node's natural
|
|
9
|
+
* shutdown does).
|
|
10
|
+
*
|
|
11
|
+
* --- Parity notes (ADR-200) ---
|
|
12
|
+
*
|
|
13
|
+
* - The Python module imports `main` from `work_engine.cli`. The Python package
|
|
14
|
+
* `__init__` emits a `DeprecationWarning` on import and registers
|
|
15
|
+
* `implement_ticket.*` → `work_engine.*` `sys.modules` aliases; those are
|
|
16
|
+
* import-resolution side effects with no observable stdout/stderr for the
|
|
17
|
+
* `-m` entrypoint (the warning is suppressed by default under `python3 -m`
|
|
18
|
+
* unless `-W` is set), so the twin imports `main` directly from the shipped
|
|
19
|
+
* `work_engine` CLI twin with no behavioural difference for this path.
|
|
20
|
+
* - `if __name__ == "__main__": sys.exit(main())` → set `process.exitCode`
|
|
21
|
+
* from `main()`; `sys.exit(int)` and a bare `process.exitCode =` produce the
|
|
22
|
+
* same process exit status without tearing down mid-flush.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { main } from '../work_engine/cli.js';
|
|
26
|
+
|
|
27
|
+
process.exitCode = main();
|