@juzser/blacksmith 0.2.0 → 0.4.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/.claude/agents/auditor.md +3 -0
- package/.claude/agents/grader.md +12 -5
- package/.claude/agents/reviewer.md +3 -0
- package/.claude/agents/security-reviewer.md +3 -0
- package/.claude/agents/spec-reviewer.md +3 -0
- package/.claude/agents/verifier.md +3 -0
- package/.claude/skills/bs/audit.md +25 -9
- package/.claude/skills/bs/dispatch.md +60 -7
- package/.claude/skills/bs/plan.md +33 -1
- package/.claude/skills/bs/run.md +18 -13
- package/.claude/skills/bs/wave.md +68 -10
- package/README.md +4 -4
- package/factory/orchestrator/dist/agentsSync.js +178 -0
- package/factory/orchestrator/dist/audit.js +134 -17
- package/factory/orchestrator/dist/autonomy.js +25 -0
- package/factory/orchestrator/dist/budgets.js +91 -2
- package/factory/orchestrator/dist/claims.js +8 -4
- package/factory/orchestrator/dist/cli.js +394 -26
- package/factory/orchestrator/dist/crossFinding.js +20 -0
- package/factory/orchestrator/dist/daemon.js +230 -41
- package/factory/orchestrator/dist/db/projector.js +93 -36
- package/factory/orchestrator/dist/db/queries.js +58 -35
- package/factory/orchestrator/dist/db/schema.js +24 -0
- package/factory/orchestrator/dist/dispatchAudit.js +10 -2
- package/factory/orchestrator/dist/dispatchLint.js +132 -0
- package/factory/orchestrator/dist/epic.js +192 -39
- package/factory/orchestrator/dist/epicWidth.js +25 -20
- package/factory/orchestrator/dist/errorIssues.js +209 -24
- package/factory/orchestrator/dist/eventOrder.js +102 -0
- package/factory/orchestrator/dist/events.js +73 -91
- package/factory/orchestrator/dist/findingContext.js +1 -13
- package/factory/orchestrator/dist/findings.js +132 -11
- package/factory/orchestrator/dist/gh.js +29 -6
- package/factory/orchestrator/dist/git.js +20 -2
- package/factory/orchestrator/dist/harness.js +92 -10
- package/factory/orchestrator/dist/hookDecision.js +320 -37
- package/factory/orchestrator/dist/impact.js +24 -2
- package/factory/orchestrator/dist/integration.js +11 -1
- package/factory/orchestrator/dist/issueReporter.js +415 -0
- package/factory/orchestrator/dist/judgeStopHook.js +181 -0
- package/factory/orchestrator/dist/judges.js +1 -1
- package/factory/orchestrator/dist/lessons.js +46 -18
- package/factory/orchestrator/dist/logCache.js +291 -0
- package/factory/orchestrator/dist/paths.js +10 -0
- package/factory/orchestrator/dist/plan.js +138 -4
- package/factory/orchestrator/dist/planQuorum.js +1 -1
- package/factory/orchestrator/dist/policy.js +57 -11
- package/factory/orchestrator/dist/projects.js +12 -1
- package/factory/orchestrator/dist/queue.js +477 -29
- package/factory/orchestrator/dist/scheduler.js +51 -0
- package/factory/orchestrator/dist/severity.js +40 -8
- package/factory/orchestrator/dist/spec.js +70 -0
- package/factory/orchestrator/dist/taskEvents.js +13 -4
- package/factory/orchestrator/dist/taskStatus.js +74 -0
- package/factory/orchestrator/dist/taxonomy.js +11 -0
- package/factory/orchestrator/dist/testgate.js +32 -3
- package/factory/orchestrator/dist/usage.js +42 -9
- package/factory/orchestrator/dist/waivers.js +60 -9
- package/factory/orchestrator/dist/waveConcurrency.js +29 -0
- package/factory/orchestrator/dist/waveNext.js +12 -10
- package/factory/orchestrator/dist/waveSchedule.js +84 -0
- package/factory/orchestrator/dist/worktree.js +19 -6
- package/factory/orchestrator/drizzle/0012_issue_reports_error_issues.sql +20 -0
- package/factory/orchestrator/drizzle/meta/_journal.json +7 -0
- package/factory/policies/budgets.yml +9 -2
- package/factory/policies/crosscheck.yml +2 -2
- package/factory/policies/lessons.md +298 -0
- package/factory/policies/scheduler.yml +27 -2
- package/factory/policies/taxonomy.yml +10 -3
- package/factory/specs/roadmap.md +70 -23
- package/package.json +8 -8
|
@@ -39,8 +39,20 @@ export class PolicyError extends SmithError {
|
|
|
39
39
|
* that reason: it is the mechanism that keeps a tester out of the code it is
|
|
40
40
|
* grading, and a policy that cannot name it either blocks the tester
|
|
41
41
|
* entirely or lets it mark its own homework.
|
|
42
|
+
*
|
|
43
|
+
* The list is closed in both directions. Every id here must appear in
|
|
44
|
+
* guardrails.yml, for the reasons above; and every id guardrails.yml declares
|
|
45
|
+
* must appear here, because a rule is a row in that file *plus* a branch in
|
|
46
|
+
* this one that looks the row up. A row with no branch parses, counts and
|
|
47
|
+
* prints like the others and enforces nothing — the file says the door is
|
|
48
|
+
* shut and no one is standing at it. So the name is what the evaluator
|
|
49
|
+
* consults, not what the file happens to carry, and `GuardrailRuleId` below
|
|
50
|
+
* makes the compiler hold every `requireRule`/`sandboxViolation` call site to
|
|
51
|
+
* it. What no type can check is a stale entry — an id left here after its
|
|
52
|
+
* branch was deleted — so policy.test.ts scrapes the call sites back out of
|
|
53
|
+
* this file and holds them against the list.
|
|
42
54
|
*/
|
|
43
|
-
const
|
|
55
|
+
export const EVALUATED_RULE_IDS = [
|
|
44
56
|
'push-to-protected',
|
|
45
57
|
'force-push',
|
|
46
58
|
'merge-into-protected',
|
|
@@ -52,6 +64,7 @@ const REQUIRED_RULE_IDS = [
|
|
|
52
64
|
'judge-sandbox-escape',
|
|
53
65
|
'role-write-scope',
|
|
54
66
|
];
|
|
67
|
+
const EVALUATED_RULE_ID_SET = new Set(EVALUATED_RULE_IDS);
|
|
55
68
|
function isStringArray(value) {
|
|
56
69
|
return Array.isArray(value) && value.every((v) => typeof v === 'string');
|
|
57
70
|
}
|
|
@@ -80,8 +93,15 @@ function parseCommandSpecs(raw, key) {
|
|
|
80
93
|
return { command: e.command, subcommands: e.subcommands };
|
|
81
94
|
});
|
|
82
95
|
}
|
|
83
|
-
/**
|
|
84
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Fold one `rules:` list into the shared id-keyed map, used for the top-level
|
|
98
|
+
* list and the two block-level ones. `declaredIn` remembers which block each
|
|
99
|
+
* id came from, so a second definition can be refused by name rather than
|
|
100
|
+
* overwriting the first: the folds run in source order, and a plain `set`
|
|
101
|
+
* would leave the last block read holding the rule while the file still reads
|
|
102
|
+
* as if the first one did.
|
|
103
|
+
*/
|
|
104
|
+
function collectRules(raw, into, declaredIn, key) {
|
|
85
105
|
if (!Array.isArray(raw)) {
|
|
86
106
|
throw new PolicyError('policy.invalid-document', `guardrails.yml is missing ${key} (a list).`, {
|
|
87
107
|
key,
|
|
@@ -94,6 +114,11 @@ function collectRules(raw, into, key) {
|
|
|
94
114
|
typeof r.reason !== 'string') {
|
|
95
115
|
throw new PolicyError('policy.invalid-document', `guardrails.yml has a rule in ${key} missing a string id/severity/reason.`, { key, rule: entry });
|
|
96
116
|
}
|
|
117
|
+
const already = declaredIn.get(r.id);
|
|
118
|
+
if (already !== undefined) {
|
|
119
|
+
throw new PolicyError('policy.invalid-document', `guardrails.yml declares rule "${r.id}" in both ${already} and ${key}; a rule gets one definition, not two.`, { rule: r.id, declaredIn: already, redeclaredIn: key });
|
|
120
|
+
}
|
|
121
|
+
declaredIn.set(r.id, key);
|
|
97
122
|
into.set(r.id, {
|
|
98
123
|
id: r.id,
|
|
99
124
|
severity: r.severity,
|
|
@@ -194,10 +219,16 @@ export function parseGuardrailPolicy(yamlText) {
|
|
|
194
219
|
}
|
|
195
220
|
const roleWriteScopes = parseRoleWriteScopes(rawScopes.scopes, judgeSandbox.roles);
|
|
196
221
|
const rules = new Map();
|
|
197
|
-
|
|
198
|
-
collectRules(
|
|
199
|
-
collectRules(
|
|
200
|
-
|
|
222
|
+
const declaredIn = new Map();
|
|
223
|
+
collectRules(doc.rules, rules, declaredIn, 'rules');
|
|
224
|
+
collectRules(rawSandbox.rules, rules, declaredIn, 'judge_sandbox.rules');
|
|
225
|
+
collectRules(rawScopes.rules, rules, declaredIn, 'role_write_scopes.rules');
|
|
226
|
+
for (const [declaredId, key] of declaredIn) {
|
|
227
|
+
if (!EVALUATED_RULE_ID_SET.has(declaredId)) {
|
|
228
|
+
throw new PolicyError('policy.invalid-document', `guardrails.yml declares rule "${declaredId}" in ${key}, which nothing in the evaluator consults; a rule with no branch to fire it enforces nothing.`, { rule: declaredId, key });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
for (const requiredId of EVALUATED_RULE_IDS) {
|
|
201
232
|
if (!rules.has(requiredId)) {
|
|
202
233
|
throw new PolicyError('policy.invalid-document', `guardrails.yml is missing required rule "${requiredId}".`, { rule: requiredId });
|
|
203
234
|
}
|
|
@@ -365,20 +396,35 @@ function renderBranch(template, branch) {
|
|
|
365
396
|
* inspect, not a hardcoded branch in `evaluateCommand` — a second Bash-like
|
|
366
397
|
* tool added later is a one-line change here, not new control flow.
|
|
367
398
|
*/
|
|
368
|
-
const INSPECTED_TOOLS = ['Bash'];
|
|
399
|
+
export const INSPECTED_TOOLS = ['Bash'];
|
|
369
400
|
/**
|
|
370
401
|
* Tools whose `file_path` this policy inspects — the write path that does
|
|
371
402
|
* not go through a shell. Nothing here has a command to match, so only the
|
|
372
403
|
* lease rules can fire on them; the base six are all about shell commands
|
|
373
404
|
* and no-op on an empty one.
|
|
405
|
+
*
|
|
406
|
+
* Exported because this evaluator is not the only place that has to know
|
|
407
|
+
* which tools write a file. `harness.ts` asks the same question from the
|
|
408
|
+
* other side: a `cli` judge is refused a worktree unless its `judge_args`
|
|
409
|
+
* deny every one of these, and a new write tool added here is a tool that
|
|
410
|
+
* harness has to start denying on the same commit. Held to that shipped
|
|
411
|
+
* policy, and to the guard hook's own `matcher`, by policy.test.ts.
|
|
374
412
|
*/
|
|
375
|
-
const INSPECTED_FILE_TOOLS = [
|
|
413
|
+
export const INSPECTED_FILE_TOOLS = [
|
|
414
|
+
'Write',
|
|
415
|
+
'Edit',
|
|
416
|
+
'MultiEdit',
|
|
417
|
+
'NotebookEdit',
|
|
418
|
+
];
|
|
376
419
|
function requireRule(policy, id) {
|
|
377
420
|
const rule = policy.rules.get(id);
|
|
378
421
|
if (!rule) {
|
|
379
422
|
// Unreachable once a policy has passed parseGuardrailPolicy (which
|
|
380
|
-
// requires every id in
|
|
381
|
-
// safety-net, not a real code path.
|
|
423
|
+
// requires every id in EVALUATED_RULE_IDS) — this is a defensive
|
|
424
|
+
// safety-net, not a real code path. Taking a `GuardrailRuleId` rather
|
|
425
|
+
// than a string is the other half: a call site that invents an id the
|
|
426
|
+
// roster does not carry does not compile, so the only way to reach a
|
|
427
|
+
// rule from here is to have declared it as one.
|
|
382
428
|
throw new PolicyError('policy.missing-rule', `guardrails.yml has no rule "${id}".`, {
|
|
383
429
|
rule: id,
|
|
384
430
|
});
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
// silent exactly when somebody is editing (D-21).
|
|
33
33
|
import { existsSync } from 'node:fs';
|
|
34
34
|
import path from 'node:path';
|
|
35
|
-
import { PROJECTS_DIR, REPO_ROOT, WORKSPACES_DIR } from './paths.js';
|
|
35
|
+
import { isFactoryCheckout, PROJECTS_DIR, REPO_ROOT, WORKSPACES_DIR } from './paths.js';
|
|
36
36
|
import { FACTORY_PROJECT, loadRoadmap } from './roadmap.js';
|
|
37
37
|
function collectProjects(opts) {
|
|
38
38
|
const roots = opts.roots ?? [PROJECTS_DIR, WORKSPACES_DIR];
|
|
@@ -58,6 +58,17 @@ function collectProjects(opts) {
|
|
|
58
58
|
missing.push({ name, roots });
|
|
59
59
|
continue;
|
|
60
60
|
}
|
|
61
|
+
// Dedupe by name alone is not enough: a self-audit's cut can stamp this
|
|
62
|
+
// clone's OWN directory name onto a milestone rather than FACTORY_PROJECT
|
|
63
|
+
// (audit.ts's `auditedProjectName`), and that name is never in `seen`.
|
|
64
|
+
// Left unchecked, `firstCheckout` resolves it to REPO_ROOT -- PROJECTS_DIR
|
|
65
|
+
// is this clone's own parent -- and this clone gets registered a second
|
|
66
|
+
// time as a foreign, non-self ref that `--no-self` cannot filter
|
|
67
|
+
// (`resolveProjectDirs` only ever drops the entry at index 0). Self
|
|
68
|
+
// already leads `refs`; a checkout that resolves to REPO_ROOT is that
|
|
69
|
+
// entry under another name, so it is skipped rather than pushed again.
|
|
70
|
+
if (isFactoryCheckout(dir))
|
|
71
|
+
continue;
|
|
61
72
|
refs.push({ name, dir, self: false });
|
|
62
73
|
}
|
|
63
74
|
return { refs, missing };
|