gennady 0.8.4-next.3 → 0.8.4-next.6
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/ai/directives/sdd/audit.directive.xml +7 -5
- package/ai/directives/sdd/phase-execution-protocol.xml +5 -5
- package/ai/skills/sdd-execute/SKILL.md +3 -3
- package/ai/skills/sdd-execute/scripts/__tests__/verify-delegation.test.ts +72 -1
- package/ai/skills/sdd-execute/scripts/lint-artifacts.sh +36 -9
- package/ai/skills/sdd-execute/scripts/verify.sh +1 -1
- package/ai/skills/sdd-execute-batch/SKILL.md +6 -4
- package/dist/ai/directives/sdd/audit.directive.xml +7 -5
- package/dist/ai/directives/sdd/phase-execution-protocol.xml +5 -5
- package/dist/ai/skills/sdd-execute/SKILL.md +3 -3
- package/dist/ai/skills/sdd-execute/scripts/__tests__/verify-delegation.test.ts +72 -1
- package/dist/ai/skills/sdd-execute/scripts/lint-artifacts.sh +36 -9
- package/dist/ai/skills/sdd-execute/scripts/verify.sh +1 -1
- package/dist/ai/skills/sdd-execute-batch/SKILL.md +6 -4
- package/dist/gennady.js +1 -1
- package/package.json +1 -1
|
@@ -87,10 +87,11 @@
|
|
|
87
87
|
</Axiom>
|
|
88
88
|
|
|
89
89
|
<Axiom id="AX_FINDINGS_AS_PROPOSALS">
|
|
90
|
-
Every finding contains a proposed remediation. Agent does not modify code.
|
|
90
|
+
Every finding contains a proposed remediation. Agent does not modify code. Remediation types:
|
|
91
91
|
- `code-fix` — concrete change in code (with location).
|
|
92
92
|
- `spec-update` — concrete spec update (module or scope) with proposed diff.
|
|
93
93
|
- `ticket-update` — ticket update (canonical case names, deferred scope, etc.).
|
|
94
|
+
- `rule-file-fix` — shared rule/directive/tooling artifact; outside any task, goes to its own ticket.
|
|
94
95
|
</Axiom>
|
|
95
96
|
|
|
96
97
|
<Axiom id="AX_FINDING_ROUTING">
|
|
@@ -108,6 +109,7 @@
|
|
|
108
109
|
| `INSIGHT_BACKFLOW` | spec edit (scope or module spec) |
|
|
109
110
|
| `STALE_AFTER_PIVOT` | reopen / refine the affected downstream artifact |
|
|
110
111
|
| `TASK_ID_DRIFT` | code-fix via ticket reopen |
|
|
112
|
+
| `RULE_FILE_INCOMPLETE` | `rule-file-fix` — the rule file is shared project infrastructure, outside every phase's Target Files. Never `ticket-update` (the ticket cannot fix it, and the paper-fix loops forever), never `phases_to_fix`, never `FAIL` for this task |
|
|
111
113
|
| operator-acknowledged risk | Decision Log entry in the scope spec |
|
|
112
114
|
|
|
113
115
|
The audit agent proposes a concrete edit (diff or instruction). The operator decides and applies. The audit agent does not modify code autonomously (per `AX_NO_AUTO_FIX`).
|
|
@@ -458,7 +460,7 @@
|
|
|
458
460
|
<Contract id="FINDING_FORMAT">
|
|
459
461
|
```markdown
|
|
460
462
|
### <🔴|🟠|🟡|🔵> F-NNN — <short title>
|
|
461
|
-
- **Type:** CLOSED_WORLD_DRIFT | COMPLETENESS_GAP | RUNTIME_BACKING_VIOLATION | RULES_COMPLIANCE_VIOLATION | RULES_CASCADE_MISMATCH | TASK_ID_DRIFT | BDD_COVERAGE_MISMATCH | EXECUTION_LOG_INCOMPLETE | STALE_AFTER_PIVOT | INSIGHT_BACKFLOW
|
|
463
|
+
- **Type:** CLOSED_WORLD_DRIFT | COMPLETENESS_GAP | RUNTIME_BACKING_VIOLATION | RULES_COMPLIANCE_VIOLATION | RULES_CASCADE_MISMATCH | TASK_ID_DRIFT | BDD_COVERAGE_MISMATCH | EXECUTION_LOG_INCOMPLETE | STALE_AFTER_PIVOT | INSIGHT_BACKFLOW | RULE_FILE_INCOMPLETE
|
|
462
464
|
- **Severity:** 🔴 BLOCKER | 🟠 MAJOR | 🟡 MINOR | 🔵 INFO
|
|
463
465
|
- **Confidence:** HIGH | MEDIUM | LOW
|
|
464
466
|
- **Status:** 📂 open | ⚠️ operator-acknowledged | ✅ resolved
|
|
@@ -482,7 +484,7 @@
|
|
|
482
484
|
|
|
483
485
|
```
|
|
484
486
|
@audit task=<Task-ID> round=<N> mode=<per-task|epic> status=<PASS|PASS_RISK|FAIL> counts=B<n>·M<n>·m<n>·I<n> phases_to_fix=[<P<N>>,...]
|
|
485
|
-
F-<NN> | sev=<B|M|m|I> | type=<TYPETOKEN> | conf=<H|M|L> | loc=<path:line|—> | phase=<P<N>|—> | src=<spec/ticket/rule anchor|—> | route=<spec-edit|ticket-reopen|ticket-update|decision-log|code-fix> | act=<one-line action, no newlines>
|
|
487
|
+
F-<NN> | sev=<B|M|m|I> | type=<TYPETOKEN> | conf=<H|M|L> | loc=<path:line|—> | phase=<P<N>|—> | src=<spec/ticket/rule anchor|—> | route=<spec-edit|ticket-reopen|ticket-update|decision-log|code-fix|rule-file-fix> | act=<one-line action, no newlines>
|
|
486
488
|
F-<NN> | …
|
|
487
489
|
~applied | <target> | <one-line description of inline change> (optional, repeat per applied change)
|
|
488
490
|
```
|
|
@@ -490,8 +492,8 @@
|
|
|
490
492
|
Token vocabulary:
|
|
491
493
|
- `sev`: `B`=BLOCKER, `M`=MAJOR, `m`=MINOR, `I`=INFO.
|
|
492
494
|
- `conf`: `H`=HIGH, `M`=MEDIUM, `L`=LOW.
|
|
493
|
-
- `type`: one of `CLOSED_WORLD_DRIFT` | `COMPLETENESS_GAP` | `RUNTIME_BACKING_VIOLATION` | `RULES_COMPLIANCE_VIOLATION` | `RULES_CASCADE_MISMATCH` | `TASK_ID_DRIFT` | `BDD_COVERAGE_MISMATCH` | `EXECUTION_LOG_INCOMPLETE` | `STALE_AFTER_PIVOT` | `INSIGHT_BACKFLOW`.
|
|
494
|
-
- `route`: one of `spec-edit` | `ticket-reopen` | `ticket-update` | `decision-log` | `code-fix` (per `AX_FINDING_ROUTING`).
|
|
495
|
+
- `type`: one of `CLOSED_WORLD_DRIFT` | `COMPLETENESS_GAP` | `RUNTIME_BACKING_VIOLATION` | `RULES_COMPLIANCE_VIOLATION` | `RULES_CASCADE_MISMATCH` | `TASK_ID_DRIFT` | `BDD_COVERAGE_MISMATCH` | `EXECUTION_LOG_INCOMPLETE` | `STALE_AFTER_PIVOT` | `INSIGHT_BACKFLOW` | `RULE_FILE_INCOMPLETE`.
|
|
496
|
+
- `route`: one of `spec-edit` | `ticket-reopen` | `ticket-update` | `decision-log` | `code-fix` | `rule-file-fix` (per `AX_FINDING_ROUTING`).
|
|
495
497
|
- `act` text: Russian operator-facing per `AX_OPERATOR_LANGUAGE` (this is operator-facing artifact text), single line, no pipe character.
|
|
496
498
|
- `status` `PASS_RISK` = PASS_WITH_ACKNOWLEDGED_RISKS.
|
|
497
499
|
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
<BeliefState>
|
|
29
29
|
<Axiom id="AX_PHASE_SCOPE_LOCK">
|
|
30
30
|
Touch only this phase's `Target Files`. Reading other files (specs, sibling-phase code, rule files cited in this phase's Rules list) is allowed and expected. Writing anywhere outside this phase's Target Files → `H_OUT_OF_PHASE_WRITE`. Operator decides via re-planning or new task.
|
|
31
|
+
|
|
32
|
+
This bounds ownership of verification failures too. A repo-wide gate that fails inside ANOTHER phase's `Target Files` is that phase's work: do not write there, do not call it resolved, do not treat it as your blocker. Record it in Handoff `open:` and continue. Everywhere else the failure is yours — including a file you never opened whose build your diff broke. Nothing outside this ticket's phases is covered by either case: that is `AX_BLOCKER_ESCALATION`.
|
|
31
33
|
</Axiom>
|
|
32
34
|
|
|
33
35
|
|
|
@@ -73,7 +75,7 @@
|
|
|
73
75
|
|
|
74
76
|
<Axiom id="AX_PERMITTED_BASH_COMMANDS">
|
|
75
77
|
Phase agent may ONLY run these bash commands:
|
|
76
|
-
- **Must run:** `<sdd-path> verify <target-files>` — MANDATORY. Auto-discovers and runs typecheck, gennady DBC lint, linter, tests, and format check for the project. Runs before §5 commands. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Failing gate → fix and re-run before EMIT_HANDOFF.
|
|
78
|
+
- **Must run:** `<sdd-path> verify <target-files>` — MANDATORY. Auto-discovers and runs typecheck, gennady DBC lint, linter, tests, and format check for the project. Runs before §5 commands. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Failing gate → fix and re-run before EMIT_HANDOFF. ONE `ver` line per invocation (see `STEP_5_VERIFY`).
|
|
77
79
|
- **Must run:** verification commands from ticket §5 that match this phase's Rules (per `AX_VERIFICATION_BEFORE_HANDOFF`).
|
|
78
80
|
- **May run:** `ls <dir>` for targeted recon (NOT `-la` or recursive); `tsc --noEmit` after code changes; `node --test <specific test file>` for test-kind phases; `date -u +%Y-%m-%dT%H:%M:%SZ` for timestamps.
|
|
79
81
|
- **May run:** `<sdd-path> extract <file> <NAME>` — extract anchored section from ticket/spec.
|
|
@@ -280,11 +282,9 @@
|
|
|
280
282
|
<Step id="STEP_5_VERIFY">
|
|
281
283
|
<Goal>Run MANDATORY sdd verify on target files, then ticket §5 commands. Log only final results.</Goal>
|
|
282
284
|
<Action>
|
|
283
|
-
1. **MANDATORY — sdd verify gate:** Run `<sdd-path> verify <target-files>`. This auto-discovers and executes typecheck, gennady DBC lint, linter, tests, and format check from package.json scripts. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line.
|
|
284
|
-
|
|
285
|
-
**⚠️ ERROR OWNERSHIP (MANDATORY):** Every error surfaced by `<sdd-path> verify` is an error of the current session. The agent is the sole actor in this repository during the session. No error may be dismissed as «not my file», «pre-existing», «someone else's problem», or «out of scope». If `<sdd-path> verify` reports a failure in ANY file — edited, created, never touched, outside Target Files, config, spec, task, generated — the agent owns it and MUST fix it. There is no «their error». There are only errors the agent has not yet fixed. Verification is holistic; so is ownership. Fix everything.
|
|
285
|
+
1. **MANDATORY — sdd verify gate:** Run `<sdd-path> verify <target-files>`. This auto-discovers and executes typecheck, gennady DBC lint, linter, tests, and format check from package.json scripts. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Any failure is yours to fix unless `AX_PHASE_SCOPE_LOCK` puts the file in another phase's hands — «pre-existing» and «not my file» are not available for the rest. Fix, re-run the full `sdd verify`, and do not proceed to EMIT_HANDOFF while a gate you own is red.
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
Log ONE `ver` line for this invocation, quoting the tool's own verdict — `sdd verify` names only the gates that FAILED, so a per-gate «pass» line reports a result it never printed. That is `fabricated-verification`, same BLOCKER class as logging a §5 command you did not run.
|
|
288
288
|
2. Then run ticket §5 commands per existing logic below.
|
|
289
289
|
2. **MANDATORY (PROTOCOL):** Execute EACH such §5 command **verbatim — the exact string from the ticket**. No substitutions, no "equivalents", no narrower variants. If §5 says `npm run check`, run `npm run check` (not `npx vitest`, not `tsc --noEmit`, not `<sdd-path> verify`). This is the Canonical Gate (per `AX_VERIFICATION_BEFORE_HANDOFF`); fabrication is a BLOCKER-class finding at audit.
|
|
290
290
|
3. **Supplemental (OPTIONAL):** You MAY additionally run `<sdd-path> verify <Target Files>` or `tsc --noEmit` for narrower diagnostics — useful for fast feedback or DBC lint. These do NOT replace §5; they add to it. Each supplemental run is logged on its own `ver` line with its real command.
|
|
@@ -95,7 +95,6 @@ Pause path (distinguish from failure — skill is awaiting operator, not broken)
|
|
|
95
95
|
4. **Close Round** — append to ticket section 7:
|
|
96
96
|
```
|
|
97
97
|
#### Round close
|
|
98
|
-
- [x] `<ts>` sync <scope>+root
|
|
99
98
|
- [x] `<ts>` DONE
|
|
100
99
|
```
|
|
101
100
|
Set ticket Meta Status → `[x] DONE`.
|
|
@@ -131,7 +130,7 @@ Pause path (distinguish from failure — skill is awaiting operator, not broken)
|
|
|
131
130
|
6. **Branch on audit status:**
|
|
132
131
|
- `PASS` or `PASS_WITH_ACKNOWLEDGED_RISKS` → ticket verified; jump to step 9 (summary).
|
|
133
132
|
- `FAIL` AND `audit_attempt = 1` → step 7 (resolve findings).
|
|
134
|
-
- `FAIL` AND `audit_attempt = 2` →
|
|
133
|
+
- `FAIL` AND `audit_attempt = 2` → cap exhausted. Set Meta Status `[!] BLOCKED`, log `🛑 BLOCKED: audit-cap-exhausted` whose `💬 unblock:` line is the literal command `/sdd-execute <TSK-NN> --new-audit-session`, sync trackers, jump to step 9.
|
|
135
134
|
|
|
136
135
|
7. **Resolve audit findings (max one retry, total 2 audit attempts):**
|
|
137
136
|
|
|
@@ -197,7 +196,8 @@ Pause path (distinguish from failure — skill is awaiting operator, not broken)
|
|
|
197
196
|
- Writing code, audit reports, or phase blocks in Execution Log. (Subagents do.)
|
|
198
197
|
- Skipping audit after all phases DONE. Audit dispatch is mandatory; this is the safety net.
|
|
199
198
|
- Sharing context between phase subagents and audit subagent. Each gets a fresh prompt; orchestrator threads only typed Handoff payloads.
|
|
200
|
-
- Audit retry beyond 2
|
|
199
|
+
- Audit retry beyond 2 attempts per session. Hard cap. Only the operator's literal `--new-audit-session` arg resets it — never your reading of «доделай» / «продолжай» / «finish it», which does not distinguish «lift the cap» from «finish what is already unblocked». No token → print the command and wait.
|
|
200
|
+
- Writing a `✅ RESOLVED` marker for a blocker that is not resolved. `check-blockers` counts markers, so one makes it dispatch — that is a bug you can trigger, not permission you can grant. The marker records a fact; fabricating it is the same class as a fabricated `ver` line.
|
|
201
201
|
- Re-running phases not flagged in `phases_to_fix`. The map finding-location → phase is the contract; do not "just re-run everything".
|
|
202
202
|
- Auto-reopening on phase BLOCKED/FAIL. Only on audit FAIL after all phases DONE the retry kicks in.
|
|
203
203
|
- Parallel dispatch of phases of the SAME task. Phases are sequential by declared `Deps`. Cross-task parallelism is the job of `sdd-execute-batch`.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// @file: Tests for verify.sh delegation probe
|
|
1
|
+
// @file: Tests for verify.sh delegation probe, the legacy classifier's mutation screen, and
|
|
2
|
+
// lint-artifacts.sh surviving sync-skills path normalization.
|
|
2
3
|
// @consumers: CI
|
|
3
4
|
// @tasks: TSK-96
|
|
4
5
|
|
|
@@ -9,10 +10,12 @@ import os from 'node:os';
|
|
|
9
10
|
import path from 'node:path';
|
|
10
11
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
11
12
|
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { normalize, SYNC_SKILLS_PATH_RULES } from '../../../../../shared/common/sync/path-normalizer.ts';
|
|
12
14
|
|
|
13
15
|
const SCRIPTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
14
16
|
const VERIFY_SH = path.join(SCRIPTS_DIR, 'verify.sh');
|
|
15
17
|
const CLASSIFIER = path.join(SCRIPTS_DIR, 'classify-scripts.js');
|
|
18
|
+
const LINT_ARTIFACTS_SH = path.join(SCRIPTS_DIR, 'lint-artifacts.sh');
|
|
16
19
|
|
|
17
20
|
/** @purpose Create a temp fixture with files, run fn, clean up. */
|
|
18
21
|
function withFixture<T>(files: Record<string, string>, fn: (dir: string) => T): T {
|
|
@@ -85,6 +88,74 @@ describe('verify.sh capability probe', () => {
|
|
|
85
88
|
});
|
|
86
89
|
});
|
|
87
90
|
|
|
91
|
+
/**
|
|
92
|
+
* @purpose Write the sync-skills-normalized copy of lint-artifacts.sh into dir and run it.
|
|
93
|
+
* @param dir Temp directory acting as the deployed project root.
|
|
94
|
+
* @param fakeGennady Shim body to expose as `gennady` on PATH; empty string exposes none.
|
|
95
|
+
* @returns Exit status and merged stdout/stderr of the deployed copy.
|
|
96
|
+
*/
|
|
97
|
+
function runDeployedLint(
|
|
98
|
+
dir: string,
|
|
99
|
+
fakeGennady: string
|
|
100
|
+
): { status: number | null; out: string } {
|
|
101
|
+
const deployed = path.join(dir, 'lint-artifacts.sh');
|
|
102
|
+
fs.writeFileSync(
|
|
103
|
+
deployed,
|
|
104
|
+
normalize(fs.readFileSync(LINT_ARTIFACTS_SH, 'utf-8'), SYNC_SKILLS_PATH_RULES)
|
|
105
|
+
);
|
|
106
|
+
fs.chmodSync(deployed, 0o755);
|
|
107
|
+
|
|
108
|
+
const bin = path.join(dir, '_bin');
|
|
109
|
+
fs.mkdirSync(bin, { recursive: true });
|
|
110
|
+
if (fakeGennady !== '') {
|
|
111
|
+
const fake = path.join(bin, 'gennady');
|
|
112
|
+
fs.writeFileSync(fake, fakeGennady);
|
|
113
|
+
fs.chmodSync(fake, 0o755);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// GENNADY_HOME points at an empty dir so only PATH resolution can succeed.
|
|
117
|
+
const proc = spawnSync('bash', [deployed, 'target.ts'], {
|
|
118
|
+
cwd: dir,
|
|
119
|
+
encoding: 'utf-8',
|
|
120
|
+
env: { ...process.env, PATH: `${bin}:${process.env.PATH}`, GENNADY_HOME: bin },
|
|
121
|
+
});
|
|
122
|
+
return { status: proc.status, out: `${proc.stdout}${proc.stderr}` };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
describe('lint-artifacts.sh after sync-skills normalization', () => {
|
|
126
|
+
// Regression: the CLI path used to live in `GENNADY_CLI=~/Developer/gennady/cli/gennady.ts`.
|
|
127
|
+
// PathNormalizer rewrote it to `GENNADY_CLI=npx gennady` — an assignment-prefixed command, not an
|
|
128
|
+
// assignment — so the variable stayed unset and `set -u` killed every deployed copy on first use.
|
|
129
|
+
it('resolves gennady from PATH instead of a normalization-mangled path literal', () => {
|
|
130
|
+
withFixture({ 'target.ts': 'export const x = 1;\n' }, (dir) => {
|
|
131
|
+
const { status, out } = runDeployedLint(
|
|
132
|
+
dir,
|
|
133
|
+
'#!/usr/bin/env bash\necho "[linting → clean] no errors"\nexit 0\n'
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
assert.equal(status, 0, out);
|
|
137
|
+
assert.match(out, /LINT_PASS/);
|
|
138
|
+
assert.ok(!out.includes('unbound variable'), `must not die on set -u: ${out}`);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('reports an actionable miss — never an unbound variable — when gennady is unreachable', () => {
|
|
143
|
+
withFixture({ 'target.ts': 'export const x = 1;\n' }, (dir) => {
|
|
144
|
+
const { status, out } = runDeployedLint(dir, '');
|
|
145
|
+
|
|
146
|
+
assert.equal(status, 1, out);
|
|
147
|
+
assert.match(out, /GENNADY_CLI_NOT_FOUND/);
|
|
148
|
+
assert.ok(!out.includes('unbound variable'), `must not die on set -u: ${out}`);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('carries no foreign absolute path into the deployed copy', () => {
|
|
153
|
+
const deployed = normalize(fs.readFileSync(LINT_ARTIFACTS_SH, 'utf-8'), SYNC_SKILLS_PATH_RULES);
|
|
154
|
+
|
|
155
|
+
assert.ok(!/\/Users\//.test(deployed), 'deployed skill must not reference a developer home');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
88
159
|
describe('classify-scripts.js mutation screen', () => {
|
|
89
160
|
it('classifies --fix/--autofix/--write bodies as mutating and never selects them', () => {
|
|
90
161
|
withFixture(
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
# @contract: AX_BASH_NO_SILENT_EMPTY — never produces empty stdout. On miss → actionable instruction.
|
|
5
5
|
#
|
|
6
6
|
# Why this wrapper exists:
|
|
7
|
-
# - gennady CLI
|
|
7
|
+
# - gennady CLI may live outside the project; resolution is done at RUNTIME (see resolve_gennady).
|
|
8
|
+
# It MUST NOT be a bare dev-path literal assigned to a variable: `sync-skills` rewrites dev paths
|
|
9
|
+
# through PathNormalizer, which turned that assignment into an unquoted two-word command, leaving
|
|
10
|
+
# the variable unset — every deployed copy then died on `set -u`. Keep this file path-literal-free.
|
|
8
11
|
# - gennady requires `node --experimental-strip-types` (Node 22+) — the bare `node` invocation is non-obvious.
|
|
9
12
|
# - gennady returns exit code 0 even when lint reports errors (the failure signal is the literal token
|
|
10
13
|
# "[linting → failed]" in stdout). We must parse output, not trust exit code.
|
|
@@ -23,7 +26,29 @@
|
|
|
23
26
|
set -uo pipefail
|
|
24
27
|
|
|
25
28
|
PROG="lint-artifacts"
|
|
26
|
-
|
|
29
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
30
|
+
|
|
31
|
+
# Repo root of a gennady checkout: scripts → sdd-execute → skills → ai → root.
|
|
32
|
+
GENNADY_HOME="${GENNADY_HOME:-$SCRIPT_DIR/../../../..}"
|
|
33
|
+
|
|
34
|
+
# Runtime resolution — deliberately contains no dev-path literal, so PathNormalizer has
|
|
35
|
+
# nothing to rewrite and the deployed copy behaves identically to the checkout copy.
|
|
36
|
+
GENNADY_ARGV=()
|
|
37
|
+
resolve_gennady() {
|
|
38
|
+
if command -v gennady &>/dev/null; then
|
|
39
|
+
GENNADY_ARGV=(gennady)
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
if [[ -x "$GENNADY_HOME/node_modules/.bin/tsx" && -f "$GENNADY_HOME/cli/gennady.ts" ]]; then
|
|
43
|
+
GENNADY_ARGV=("$GENNADY_HOME/node_modules/.bin/tsx" "$GENNADY_HOME/cli/gennady.ts")
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
if [[ -x "./node_modules/.bin/gennady" ]]; then
|
|
47
|
+
GENNADY_ARGV=(./node_modules/.bin/gennady)
|
|
48
|
+
return 0
|
|
49
|
+
fi
|
|
50
|
+
return 1
|
|
51
|
+
}
|
|
27
52
|
|
|
28
53
|
if [[ $# -lt 1 ]]; then
|
|
29
54
|
cat <<EOF
|
|
@@ -38,16 +63,18 @@ EOF
|
|
|
38
63
|
exit 4
|
|
39
64
|
fi
|
|
40
65
|
|
|
41
|
-
if
|
|
66
|
+
if ! resolve_gennady; then
|
|
42
67
|
cat <<EOF
|
|
43
68
|
[$PROG] GENNADY_CLI_NOT_FOUND
|
|
44
|
-
|
|
69
|
+
tried: gennady on PATH
|
|
70
|
+
\$GENNADY_HOME/cli/gennady.ts via checkout tsx (GENNADY_HOME=$GENNADY_HOME)
|
|
71
|
+
./node_modules/.bin/gennady
|
|
45
72
|
|
|
46
73
|
Diagnosis: the gennady AST DbC linter is unreachable from this environment.
|
|
47
74
|
|
|
48
75
|
Required action (ORCHESTRATOR):
|
|
49
|
-
1.
|
|
50
|
-
2.
|
|
76
|
+
1. Install it in the project (\`npm i -D gennady\`) or put it on PATH.
|
|
77
|
+
2. Working from a gennady checkout → export GENNADY_HOME=<checkout-root>.
|
|
51
78
|
3. If on a CI/sandbox without gennady → this is a HARD blocker; phase cannot verify.
|
|
52
79
|
Report to operator: cannot complete phase without DBC contract verification.
|
|
53
80
|
|
|
@@ -65,7 +92,7 @@ tmp_out=$(mktemp -t lint-artifacts.XXXXXX)
|
|
|
65
92
|
trap 'rm -f "$tmp_out"' EXIT
|
|
66
93
|
|
|
67
94
|
# Run gennady. We intentionally ignore its exit code (unreliable per contract above).
|
|
68
|
-
|
|
95
|
+
"${GENNADY_ARGV[@]}" lint "$@" > "$tmp_out" 2>&1 || true
|
|
69
96
|
|
|
70
97
|
has_clean=$(grep -c '\[linting → clean\]' "$tmp_out" 2>/dev/null || echo 0)
|
|
71
98
|
has_failed=$(grep -c '\[linting → failed\]' "$tmp_out" 2>/dev/null || echo 0)
|
|
@@ -108,7 +135,7 @@ Required action (PHASE AGENT, before EMIT_HANDOFF):
|
|
|
108
135
|
5. Do NOT EMIT_HANDOFF with lint failures present — that is fabricated DONE.
|
|
109
136
|
|
|
110
137
|
References:
|
|
111
|
-
|
|
138
|
+
ai/directives/coding/typescript-rules.xml
|
|
112
139
|
— AX_TAG_USAGE_MATRIX, AX_BASE_CONTRACT_SHAPE, AX_FLAT_JSDOC_FOR_PROPERTIES
|
|
113
140
|
EOF
|
|
114
141
|
exit 2
|
|
@@ -135,7 +162,7 @@ Captured output:
|
|
|
135
162
|
$(cat "$tmp_out" | head -50)
|
|
136
163
|
|
|
137
164
|
Required action (ORCHESTRATOR):
|
|
138
|
-
1. Re-read gennady source
|
|
165
|
+
1. Re-read the gennady LintCommand source in the checkout you resolved above.
|
|
139
166
|
2. Check command output tokens in the LintCommand implementation.
|
|
140
167
|
3. Update this script's parsing to match new tokens.
|
|
141
168
|
4. Until resolved → treat as a HARD blocker; DO NOT assume PASS.
|
|
@@ -174,7 +174,7 @@ run_cmd() {
|
|
|
174
174
|
while IFS=: read cls name; do
|
|
175
175
|
case "$cls" in
|
|
176
176
|
typecheck) run_cmd "typecheck" "npm run $name" npm run "$name" || true ;;
|
|
177
|
-
gennady) run_cmd "gennady DBC lint" "gennady lint ${#FILES[@]} files"
|
|
177
|
+
gennady) run_cmd "gennady DBC lint" "gennady lint ${#FILES[@]} files" "$SCRIPT_DIR/lint-artifacts.sh" "${FILES[@]}" || true ;;
|
|
178
178
|
lint) run_cmd "lint" "npm run $name" npm run "$name" || true ;;
|
|
179
179
|
test) run_cmd "test" "npm run $name" npm run "$name" || true ;;
|
|
180
180
|
format) run_cmd "format check" "npm run $name" npm run "$name" || true ;;
|
|
@@ -125,12 +125,13 @@ Per-task phase tokens:
|
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
b. Branch on phase status:
|
|
128
|
-
- `BLOCKED`
|
|
128
|
+
- `BLOCKED` on a repo-wide gate failing in files this ticket does not own → PARK the lane, do not fail it. Parallel lanes share one working tree, so that is usually a sibling lane mid-flight. Resume each parked lane once after the sub-batch drains; still blocked → `✋ AWAITING UNBLOCK`, not `❌ FAILED`.
|
|
129
|
+
- `BLOCKED` or `FAIL` otherwise → STOP this task's lane; mark task FAILED for the batch. Other parallel tasks in same sub-batch continue.
|
|
129
130
|
- `DONE` → record Handoff (artifacts, decisions, open). Continue to next phase.
|
|
130
131
|
|
|
131
132
|
c. Thread next phase's Inputs from this phase's Handoff (verbatim).
|
|
132
133
|
|
|
133
|
-
After all phases DONE: close Round (append `#### Round close` block
|
|
134
|
+
After all phases DONE: close Round (append `#### Round close` block per `ROUND_CLOSE_FORMAT`: a single `DONE` line). Sync trackers. Ticket Status → `[x] DONE`.
|
|
134
135
|
|
|
135
136
|
d. Dispatch AUDIT subagent (`subagent_type: general-purpose`, **`model: "haiku"`** — audit is mechanical verification + fact-checking, haiku sufficient and cheaper). MANDATORY, always runs. Include in prompt the SDD tooling location: `~/Developer/gennady/ai/skills/sdd-execute/scripts/sdd` (audit may use `lint`, `verify`, `check-blockers` subcommands):
|
|
136
137
|
```
|
|
@@ -161,7 +162,7 @@ Per-task phase tokens:
|
|
|
161
162
|
On BLOCKED/FAIL → STOP this task's lane; mark task FAILED.
|
|
162
163
|
After all fix phases DONE → close Round → dispatch AUDIT (round 2, fresh context). Branch again:
|
|
163
164
|
PASS → task complete.
|
|
164
|
-
FAIL (audit_attempt = 2) → STOP lane;
|
|
165
|
+
FAIL (audit_attempt = 2) → STOP lane; cap exhausted. Meta Status `[!] BLOCKED`, `🛑 BLOCKED: audit-cap-exhausted` with `💬 unblock: /sdd-execute <TSK-NN> --new-audit-session`. Report as `🛑 cap-exhausted`, distinct from `❌ FAILED`. The batch never lifts the cap itself.
|
|
165
166
|
|
|
166
167
|
— Wait for all parallel task lanes in sub-batch to finish.
|
|
167
168
|
— Any task FAILED → continue batch (other layers may not depend on it; if they do they'll be marked `⏸️ waiting`). Operator gets failure list in final summary.
|
|
@@ -202,7 +203,8 @@ Per-task phase tokens:
|
|
|
202
203
|
- Writing code or phase blocks in Execution Log. (Phase subagents do.)
|
|
203
204
|
- Sharing context between phase subagents of different tasks. Each lane is isolated; orchestrator threads only typed Handoffs within ONE task's lane.
|
|
204
205
|
- Skipping audit after Round close in default (per-task) mode. Audit dispatch is mandatory.
|
|
205
|
-
- Audit retry beyond 2
|
|
206
|
+
- Audit retry beyond 2 attempts per task per session. Hard cap; only the operator's literal `--new-audit-session` arg resets it, and only via the sibling `sdd-execute` skill.
|
|
207
|
+
- Writing a `✅ RESOLVED` marker for a blocker that is not resolved, to get past the `check-blockers` preflight. The marker records a fact, not permission.
|
|
206
208
|
- Re-running phases not flagged in `phases_to_fix`. The map finding-location → phase is the contract.
|
|
207
209
|
- Parallel dispatch ACROSS layers. Layers run sequentially.
|
|
208
210
|
- Parallel dispatch ACROSS sub-batches in same layer. Sub-batches exist exactly because of file conflicts.
|
|
@@ -87,10 +87,11 @@
|
|
|
87
87
|
</Axiom>
|
|
88
88
|
|
|
89
89
|
<Axiom id="AX_FINDINGS_AS_PROPOSALS">
|
|
90
|
-
Every finding contains a proposed remediation. Agent does not modify code.
|
|
90
|
+
Every finding contains a proposed remediation. Agent does not modify code. Remediation types:
|
|
91
91
|
- `code-fix` — concrete change in code (with location).
|
|
92
92
|
- `spec-update` — concrete spec update (module or scope) with proposed diff.
|
|
93
93
|
- `ticket-update` — ticket update (canonical case names, deferred scope, etc.).
|
|
94
|
+
- `rule-file-fix` — shared rule/directive/tooling artifact; outside any task, goes to its own ticket.
|
|
94
95
|
</Axiom>
|
|
95
96
|
|
|
96
97
|
<Axiom id="AX_FINDING_ROUTING">
|
|
@@ -108,6 +109,7 @@
|
|
|
108
109
|
| `INSIGHT_BACKFLOW` | spec edit (scope or module spec) |
|
|
109
110
|
| `STALE_AFTER_PIVOT` | reopen / refine the affected downstream artifact |
|
|
110
111
|
| `TASK_ID_DRIFT` | code-fix via ticket reopen |
|
|
112
|
+
| `RULE_FILE_INCOMPLETE` | `rule-file-fix` — the rule file is shared project infrastructure, outside every phase's Target Files. Never `ticket-update` (the ticket cannot fix it, and the paper-fix loops forever), never `phases_to_fix`, never `FAIL` for this task |
|
|
111
113
|
| operator-acknowledged risk | Decision Log entry in the scope spec |
|
|
112
114
|
|
|
113
115
|
The audit agent proposes a concrete edit (diff or instruction). The operator decides and applies. The audit agent does not modify code autonomously (per `AX_NO_AUTO_FIX`).
|
|
@@ -458,7 +460,7 @@
|
|
|
458
460
|
<Contract id="FINDING_FORMAT">
|
|
459
461
|
```markdown
|
|
460
462
|
### <🔴|🟠|🟡|🔵> F-NNN — <short title>
|
|
461
|
-
- **Type:** CLOSED_WORLD_DRIFT | COMPLETENESS_GAP | RUNTIME_BACKING_VIOLATION | RULES_COMPLIANCE_VIOLATION | RULES_CASCADE_MISMATCH | TASK_ID_DRIFT | BDD_COVERAGE_MISMATCH | EXECUTION_LOG_INCOMPLETE | STALE_AFTER_PIVOT | INSIGHT_BACKFLOW
|
|
463
|
+
- **Type:** CLOSED_WORLD_DRIFT | COMPLETENESS_GAP | RUNTIME_BACKING_VIOLATION | RULES_COMPLIANCE_VIOLATION | RULES_CASCADE_MISMATCH | TASK_ID_DRIFT | BDD_COVERAGE_MISMATCH | EXECUTION_LOG_INCOMPLETE | STALE_AFTER_PIVOT | INSIGHT_BACKFLOW | RULE_FILE_INCOMPLETE
|
|
462
464
|
- **Severity:** 🔴 BLOCKER | 🟠 MAJOR | 🟡 MINOR | 🔵 INFO
|
|
463
465
|
- **Confidence:** HIGH | MEDIUM | LOW
|
|
464
466
|
- **Status:** 📂 open | ⚠️ operator-acknowledged | ✅ resolved
|
|
@@ -482,7 +484,7 @@
|
|
|
482
484
|
|
|
483
485
|
```
|
|
484
486
|
@audit task=<Task-ID> round=<N> mode=<per-task|epic> status=<PASS|PASS_RISK|FAIL> counts=B<n>·M<n>·m<n>·I<n> phases_to_fix=[<P<N>>,...]
|
|
485
|
-
F-<NN> | sev=<B|M|m|I> | type=<TYPETOKEN> | conf=<H|M|L> | loc=<path:line|—> | phase=<P<N>|—> | src=<spec/ticket/rule anchor|—> | route=<spec-edit|ticket-reopen|ticket-update|decision-log|code-fix> | act=<one-line action, no newlines>
|
|
487
|
+
F-<NN> | sev=<B|M|m|I> | type=<TYPETOKEN> | conf=<H|M|L> | loc=<path:line|—> | phase=<P<N>|—> | src=<spec/ticket/rule anchor|—> | route=<spec-edit|ticket-reopen|ticket-update|decision-log|code-fix|rule-file-fix> | act=<one-line action, no newlines>
|
|
486
488
|
F-<NN> | …
|
|
487
489
|
~applied | <target> | <one-line description of inline change> (optional, repeat per applied change)
|
|
488
490
|
```
|
|
@@ -490,8 +492,8 @@
|
|
|
490
492
|
Token vocabulary:
|
|
491
493
|
- `sev`: `B`=BLOCKER, `M`=MAJOR, `m`=MINOR, `I`=INFO.
|
|
492
494
|
- `conf`: `H`=HIGH, `M`=MEDIUM, `L`=LOW.
|
|
493
|
-
- `type`: one of `CLOSED_WORLD_DRIFT` | `COMPLETENESS_GAP` | `RUNTIME_BACKING_VIOLATION` | `RULES_COMPLIANCE_VIOLATION` | `RULES_CASCADE_MISMATCH` | `TASK_ID_DRIFT` | `BDD_COVERAGE_MISMATCH` | `EXECUTION_LOG_INCOMPLETE` | `STALE_AFTER_PIVOT` | `INSIGHT_BACKFLOW`.
|
|
494
|
-
- `route`: one of `spec-edit` | `ticket-reopen` | `ticket-update` | `decision-log` | `code-fix` (per `AX_FINDING_ROUTING`).
|
|
495
|
+
- `type`: one of `CLOSED_WORLD_DRIFT` | `COMPLETENESS_GAP` | `RUNTIME_BACKING_VIOLATION` | `RULES_COMPLIANCE_VIOLATION` | `RULES_CASCADE_MISMATCH` | `TASK_ID_DRIFT` | `BDD_COVERAGE_MISMATCH` | `EXECUTION_LOG_INCOMPLETE` | `STALE_AFTER_PIVOT` | `INSIGHT_BACKFLOW` | `RULE_FILE_INCOMPLETE`.
|
|
496
|
+
- `route`: one of `spec-edit` | `ticket-reopen` | `ticket-update` | `decision-log` | `code-fix` | `rule-file-fix` (per `AX_FINDING_ROUTING`).
|
|
495
497
|
- `act` text: Russian operator-facing per `AX_OPERATOR_LANGUAGE` (this is operator-facing artifact text), single line, no pipe character.
|
|
496
498
|
- `status` `PASS_RISK` = PASS_WITH_ACKNOWLEDGED_RISKS.
|
|
497
499
|
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
<BeliefState>
|
|
29
29
|
<Axiom id="AX_PHASE_SCOPE_LOCK">
|
|
30
30
|
Touch only this phase's `Target Files`. Reading other files (specs, sibling-phase code, rule files cited in this phase's Rules list) is allowed and expected. Writing anywhere outside this phase's Target Files → `H_OUT_OF_PHASE_WRITE`. Operator decides via re-planning or new task.
|
|
31
|
+
|
|
32
|
+
This bounds ownership of verification failures too. A repo-wide gate that fails inside ANOTHER phase's `Target Files` is that phase's work: do not write there, do not call it resolved, do not treat it as your blocker. Record it in Handoff `open:` and continue. Everywhere else the failure is yours — including a file you never opened whose build your diff broke. Nothing outside this ticket's phases is covered by either case: that is `AX_BLOCKER_ESCALATION`.
|
|
31
33
|
</Axiom>
|
|
32
34
|
|
|
33
35
|
|
|
@@ -73,7 +75,7 @@
|
|
|
73
75
|
|
|
74
76
|
<Axiom id="AX_PERMITTED_BASH_COMMANDS">
|
|
75
77
|
Phase agent may ONLY run these bash commands:
|
|
76
|
-
- **Must run:** `<sdd-path> verify <target-files>` — MANDATORY. Auto-discovers and runs typecheck, gennady DBC lint, linter, tests, and format check for the project. Runs before §5 commands. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Failing gate → fix and re-run before EMIT_HANDOFF.
|
|
78
|
+
- **Must run:** `<sdd-path> verify <target-files>` — MANDATORY. Auto-discovers and runs typecheck, gennady DBC lint, linter, tests, and format check for the project. Runs before §5 commands. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Failing gate → fix and re-run before EMIT_HANDOFF. ONE `ver` line per invocation (see `STEP_5_VERIFY`).
|
|
77
79
|
- **Must run:** verification commands from ticket §5 that match this phase's Rules (per `AX_VERIFICATION_BEFORE_HANDOFF`).
|
|
78
80
|
- **May run:** `ls <dir>` for targeted recon (NOT `-la` or recursive); `tsc --noEmit` after code changes; `node --test <specific test file>` for test-kind phases; `date -u +%Y-%m-%dT%H:%M:%SZ` for timestamps.
|
|
79
81
|
- **May run:** `<sdd-path> extract <file> <NAME>` — extract anchored section from ticket/spec.
|
|
@@ -280,11 +282,9 @@
|
|
|
280
282
|
<Step id="STEP_5_VERIFY">
|
|
281
283
|
<Goal>Run MANDATORY sdd verify on target files, then ticket §5 commands. Log only final results.</Goal>
|
|
282
284
|
<Action>
|
|
283
|
-
1. **MANDATORY — sdd verify gate:** Run `<sdd-path> verify <target-files>`. This auto-discovers and executes typecheck, gennady DBC lint, linter, tests, and format check from package.json scripts. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line.
|
|
284
|
-
|
|
285
|
-
**⚠️ ERROR OWNERSHIP (MANDATORY):** Every error surfaced by `<sdd-path> verify` is an error of the current session. The agent is the sole actor in this repository during the session. No error may be dismissed as «not my file», «pre-existing», «someone else's problem», or «out of scope». If `<sdd-path> verify` reports a failure in ANY file — edited, created, never touched, outside Target Files, config, spec, task, generated — the agent owns it and MUST fix it. There is no «their error». There are only errors the agent has not yet fixed. Verification is holistic; so is ownership. Fix everything.
|
|
285
|
+
1. **MANDATORY — sdd verify gate:** Run `<sdd-path> verify <target-files>`. This auto-discovers and executes typecheck, gennady DBC lint, linter, tests, and format check from package.json scripts. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Any failure is yours to fix unless `AX_PHASE_SCOPE_LOCK` puts the file in another phase's hands — «pre-existing» and «not my file» are not available for the rest. Fix, re-run the full `sdd verify`, and do not proceed to EMIT_HANDOFF while a gate you own is red.
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
Log ONE `ver` line for this invocation, quoting the tool's own verdict — `sdd verify` names only the gates that FAILED, so a per-gate «pass» line reports a result it never printed. That is `fabricated-verification`, same BLOCKER class as logging a §5 command you did not run.
|
|
288
288
|
2. Then run ticket §5 commands per existing logic below.
|
|
289
289
|
2. **MANDATORY (PROTOCOL):** Execute EACH such §5 command **verbatim — the exact string from the ticket**. No substitutions, no "equivalents", no narrower variants. If §5 says `npm run check`, run `npm run check` (not `npx vitest`, not `tsc --noEmit`, not `<sdd-path> verify`). This is the Canonical Gate (per `AX_VERIFICATION_BEFORE_HANDOFF`); fabrication is a BLOCKER-class finding at audit.
|
|
290
290
|
3. **Supplemental (OPTIONAL):** You MAY additionally run `<sdd-path> verify <Target Files>` or `tsc --noEmit` for narrower diagnostics — useful for fast feedback or DBC lint. These do NOT replace §5; they add to it. Each supplemental run is logged on its own `ver` line with its real command.
|
|
@@ -95,7 +95,6 @@ Pause path (distinguish from failure — skill is awaiting operator, not broken)
|
|
|
95
95
|
4. **Close Round** — append to ticket section 7:
|
|
96
96
|
```
|
|
97
97
|
#### Round close
|
|
98
|
-
- [x] `<ts>` sync <scope>+root
|
|
99
98
|
- [x] `<ts>` DONE
|
|
100
99
|
```
|
|
101
100
|
Set ticket Meta Status → `[x] DONE`.
|
|
@@ -131,7 +130,7 @@ Pause path (distinguish from failure — skill is awaiting operator, not broken)
|
|
|
131
130
|
6. **Branch on audit status:**
|
|
132
131
|
- `PASS` or `PASS_WITH_ACKNOWLEDGED_RISKS` → ticket verified; jump to step 9 (summary).
|
|
133
132
|
- `FAIL` AND `audit_attempt = 1` → step 7 (resolve findings).
|
|
134
|
-
- `FAIL` AND `audit_attempt = 2` →
|
|
133
|
+
- `FAIL` AND `audit_attempt = 2` → cap exhausted. Set Meta Status `[!] BLOCKED`, log `🛑 BLOCKED: audit-cap-exhausted` whose `💬 unblock:` line is the literal command `/sdd-execute <TSK-NN> --new-audit-session`, sync trackers, jump to step 9.
|
|
135
134
|
|
|
136
135
|
7. **Resolve audit findings (max one retry, total 2 audit attempts):**
|
|
137
136
|
|
|
@@ -197,7 +196,8 @@ Pause path (distinguish from failure — skill is awaiting operator, not broken)
|
|
|
197
196
|
- Writing code, audit reports, or phase blocks in Execution Log. (Subagents do.)
|
|
198
197
|
- Skipping audit after all phases DONE. Audit dispatch is mandatory; this is the safety net.
|
|
199
198
|
- Sharing context between phase subagents and audit subagent. Each gets a fresh prompt; orchestrator threads only typed Handoff payloads.
|
|
200
|
-
- Audit retry beyond 2
|
|
199
|
+
- Audit retry beyond 2 attempts per session. Hard cap. Only the operator's literal `--new-audit-session` arg resets it — never your reading of «доделай» / «продолжай» / «finish it», which does not distinguish «lift the cap» from «finish what is already unblocked». No token → print the command and wait.
|
|
200
|
+
- Writing a `✅ RESOLVED` marker for a blocker that is not resolved. `check-blockers` counts markers, so one makes it dispatch — that is a bug you can trigger, not permission you can grant. The marker records a fact; fabricating it is the same class as a fabricated `ver` line.
|
|
201
201
|
- Re-running phases not flagged in `phases_to_fix`. The map finding-location → phase is the contract; do not "just re-run everything".
|
|
202
202
|
- Auto-reopening on phase BLOCKED/FAIL. Only on audit FAIL after all phases DONE the retry kicks in.
|
|
203
203
|
- Parallel dispatch of phases of the SAME task. Phases are sequential by declared `Deps`. Cross-task parallelism is the job of `sdd-execute-batch`.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// @file: Tests for verify.sh delegation probe
|
|
1
|
+
// @file: Tests for verify.sh delegation probe, the legacy classifier's mutation screen, and
|
|
2
|
+
// lint-artifacts.sh surviving sync-skills path normalization.
|
|
2
3
|
// @consumers: CI
|
|
3
4
|
// @tasks: TSK-96
|
|
4
5
|
|
|
@@ -9,10 +10,12 @@ import os from 'node:os';
|
|
|
9
10
|
import path from 'node:path';
|
|
10
11
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
11
12
|
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { normalize, SYNC_SKILLS_PATH_RULES } from '../../../../../shared/common/sync/path-normalizer.ts';
|
|
12
14
|
|
|
13
15
|
const SCRIPTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
14
16
|
const VERIFY_SH = path.join(SCRIPTS_DIR, 'verify.sh');
|
|
15
17
|
const CLASSIFIER = path.join(SCRIPTS_DIR, 'classify-scripts.js');
|
|
18
|
+
const LINT_ARTIFACTS_SH = path.join(SCRIPTS_DIR, 'lint-artifacts.sh');
|
|
16
19
|
|
|
17
20
|
/** @purpose Create a temp fixture with files, run fn, clean up. */
|
|
18
21
|
function withFixture<T>(files: Record<string, string>, fn: (dir: string) => T): T {
|
|
@@ -85,6 +88,74 @@ describe('verify.sh capability probe', () => {
|
|
|
85
88
|
});
|
|
86
89
|
});
|
|
87
90
|
|
|
91
|
+
/**
|
|
92
|
+
* @purpose Write the sync-skills-normalized copy of lint-artifacts.sh into dir and run it.
|
|
93
|
+
* @param dir Temp directory acting as the deployed project root.
|
|
94
|
+
* @param fakeGennady Shim body to expose as `gennady` on PATH; empty string exposes none.
|
|
95
|
+
* @returns Exit status and merged stdout/stderr of the deployed copy.
|
|
96
|
+
*/
|
|
97
|
+
function runDeployedLint(
|
|
98
|
+
dir: string,
|
|
99
|
+
fakeGennady: string
|
|
100
|
+
): { status: number | null; out: string } {
|
|
101
|
+
const deployed = path.join(dir, 'lint-artifacts.sh');
|
|
102
|
+
fs.writeFileSync(
|
|
103
|
+
deployed,
|
|
104
|
+
normalize(fs.readFileSync(LINT_ARTIFACTS_SH, 'utf-8'), SYNC_SKILLS_PATH_RULES)
|
|
105
|
+
);
|
|
106
|
+
fs.chmodSync(deployed, 0o755);
|
|
107
|
+
|
|
108
|
+
const bin = path.join(dir, '_bin');
|
|
109
|
+
fs.mkdirSync(bin, { recursive: true });
|
|
110
|
+
if (fakeGennady !== '') {
|
|
111
|
+
const fake = path.join(bin, 'gennady');
|
|
112
|
+
fs.writeFileSync(fake, fakeGennady);
|
|
113
|
+
fs.chmodSync(fake, 0o755);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// GENNADY_HOME points at an empty dir so only PATH resolution can succeed.
|
|
117
|
+
const proc = spawnSync('bash', [deployed, 'target.ts'], {
|
|
118
|
+
cwd: dir,
|
|
119
|
+
encoding: 'utf-8',
|
|
120
|
+
env: { ...process.env, PATH: `${bin}:${process.env.PATH}`, GENNADY_HOME: bin },
|
|
121
|
+
});
|
|
122
|
+
return { status: proc.status, out: `${proc.stdout}${proc.stderr}` };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
describe('lint-artifacts.sh after sync-skills normalization', () => {
|
|
126
|
+
// Regression: the CLI path used to live in `GENNADY_CLI=~/Developer/gennady/cli/gennady.ts`.
|
|
127
|
+
// PathNormalizer rewrote it to `GENNADY_CLI=npx gennady` — an assignment-prefixed command, not an
|
|
128
|
+
// assignment — so the variable stayed unset and `set -u` killed every deployed copy on first use.
|
|
129
|
+
it('resolves gennady from PATH instead of a normalization-mangled path literal', () => {
|
|
130
|
+
withFixture({ 'target.ts': 'export const x = 1;\n' }, (dir) => {
|
|
131
|
+
const { status, out } = runDeployedLint(
|
|
132
|
+
dir,
|
|
133
|
+
'#!/usr/bin/env bash\necho "[linting → clean] no errors"\nexit 0\n'
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
assert.equal(status, 0, out);
|
|
137
|
+
assert.match(out, /LINT_PASS/);
|
|
138
|
+
assert.ok(!out.includes('unbound variable'), `must not die on set -u: ${out}`);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('reports an actionable miss — never an unbound variable — when gennady is unreachable', () => {
|
|
143
|
+
withFixture({ 'target.ts': 'export const x = 1;\n' }, (dir) => {
|
|
144
|
+
const { status, out } = runDeployedLint(dir, '');
|
|
145
|
+
|
|
146
|
+
assert.equal(status, 1, out);
|
|
147
|
+
assert.match(out, /GENNADY_CLI_NOT_FOUND/);
|
|
148
|
+
assert.ok(!out.includes('unbound variable'), `must not die on set -u: ${out}`);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('carries no foreign absolute path into the deployed copy', () => {
|
|
153
|
+
const deployed = normalize(fs.readFileSync(LINT_ARTIFACTS_SH, 'utf-8'), SYNC_SKILLS_PATH_RULES);
|
|
154
|
+
|
|
155
|
+
assert.ok(!/\/Users\//.test(deployed), 'deployed skill must not reference a developer home');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
88
159
|
describe('classify-scripts.js mutation screen', () => {
|
|
89
160
|
it('classifies --fix/--autofix/--write bodies as mutating and never selects them', () => {
|
|
90
161
|
withFixture(
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
# @contract: AX_BASH_NO_SILENT_EMPTY — never produces empty stdout. On miss → actionable instruction.
|
|
5
5
|
#
|
|
6
6
|
# Why this wrapper exists:
|
|
7
|
-
# - gennady CLI
|
|
7
|
+
# - gennady CLI may live outside the project; resolution is done at RUNTIME (see resolve_gennady).
|
|
8
|
+
# It MUST NOT be a bare dev-path literal assigned to a variable: `sync-skills` rewrites dev paths
|
|
9
|
+
# through PathNormalizer, which turned that assignment into an unquoted two-word command, leaving
|
|
10
|
+
# the variable unset — every deployed copy then died on `set -u`. Keep this file path-literal-free.
|
|
8
11
|
# - gennady requires `node --experimental-strip-types` (Node 22+) — the bare `node` invocation is non-obvious.
|
|
9
12
|
# - gennady returns exit code 0 even when lint reports errors (the failure signal is the literal token
|
|
10
13
|
# "[linting → failed]" in stdout). We must parse output, not trust exit code.
|
|
@@ -23,7 +26,29 @@
|
|
|
23
26
|
set -uo pipefail
|
|
24
27
|
|
|
25
28
|
PROG="lint-artifacts"
|
|
26
|
-
|
|
29
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
30
|
+
|
|
31
|
+
# Repo root of a gennady checkout: scripts → sdd-execute → skills → ai → root.
|
|
32
|
+
GENNADY_HOME="${GENNADY_HOME:-$SCRIPT_DIR/../../../..}"
|
|
33
|
+
|
|
34
|
+
# Runtime resolution — deliberately contains no dev-path literal, so PathNormalizer has
|
|
35
|
+
# nothing to rewrite and the deployed copy behaves identically to the checkout copy.
|
|
36
|
+
GENNADY_ARGV=()
|
|
37
|
+
resolve_gennady() {
|
|
38
|
+
if command -v gennady &>/dev/null; then
|
|
39
|
+
GENNADY_ARGV=(gennady)
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
if [[ -x "$GENNADY_HOME/node_modules/.bin/tsx" && -f "$GENNADY_HOME/cli/gennady.ts" ]]; then
|
|
43
|
+
GENNADY_ARGV=("$GENNADY_HOME/node_modules/.bin/tsx" "$GENNADY_HOME/cli/gennady.ts")
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
if [[ -x "./node_modules/.bin/gennady" ]]; then
|
|
47
|
+
GENNADY_ARGV=(./node_modules/.bin/gennady)
|
|
48
|
+
return 0
|
|
49
|
+
fi
|
|
50
|
+
return 1
|
|
51
|
+
}
|
|
27
52
|
|
|
28
53
|
if [[ $# -lt 1 ]]; then
|
|
29
54
|
cat <<EOF
|
|
@@ -38,16 +63,18 @@ EOF
|
|
|
38
63
|
exit 4
|
|
39
64
|
fi
|
|
40
65
|
|
|
41
|
-
if
|
|
66
|
+
if ! resolve_gennady; then
|
|
42
67
|
cat <<EOF
|
|
43
68
|
[$PROG] GENNADY_CLI_NOT_FOUND
|
|
44
|
-
|
|
69
|
+
tried: gennady on PATH
|
|
70
|
+
\$GENNADY_HOME/cli/gennady.ts via checkout tsx (GENNADY_HOME=$GENNADY_HOME)
|
|
71
|
+
./node_modules/.bin/gennady
|
|
45
72
|
|
|
46
73
|
Diagnosis: the gennady AST DbC linter is unreachable from this environment.
|
|
47
74
|
|
|
48
75
|
Required action (ORCHESTRATOR):
|
|
49
|
-
1.
|
|
50
|
-
2.
|
|
76
|
+
1. Install it in the project (\`npm i -D gennady\`) or put it on PATH.
|
|
77
|
+
2. Working from a gennady checkout → export GENNADY_HOME=<checkout-root>.
|
|
51
78
|
3. If on a CI/sandbox without gennady → this is a HARD blocker; phase cannot verify.
|
|
52
79
|
Report to operator: cannot complete phase without DBC contract verification.
|
|
53
80
|
|
|
@@ -65,7 +92,7 @@ tmp_out=$(mktemp -t lint-artifacts.XXXXXX)
|
|
|
65
92
|
trap 'rm -f "$tmp_out"' EXIT
|
|
66
93
|
|
|
67
94
|
# Run gennady. We intentionally ignore its exit code (unreliable per contract above).
|
|
68
|
-
|
|
95
|
+
"${GENNADY_ARGV[@]}" lint "$@" > "$tmp_out" 2>&1 || true
|
|
69
96
|
|
|
70
97
|
has_clean=$(grep -c '\[linting → clean\]' "$tmp_out" 2>/dev/null || echo 0)
|
|
71
98
|
has_failed=$(grep -c '\[linting → failed\]' "$tmp_out" 2>/dev/null || echo 0)
|
|
@@ -108,7 +135,7 @@ Required action (PHASE AGENT, before EMIT_HANDOFF):
|
|
|
108
135
|
5. Do NOT EMIT_HANDOFF with lint failures present — that is fabricated DONE.
|
|
109
136
|
|
|
110
137
|
References:
|
|
111
|
-
|
|
138
|
+
ai/directives/coding/typescript-rules.xml
|
|
112
139
|
— AX_TAG_USAGE_MATRIX, AX_BASE_CONTRACT_SHAPE, AX_FLAT_JSDOC_FOR_PROPERTIES
|
|
113
140
|
EOF
|
|
114
141
|
exit 2
|
|
@@ -135,7 +162,7 @@ Captured output:
|
|
|
135
162
|
$(cat "$tmp_out" | head -50)
|
|
136
163
|
|
|
137
164
|
Required action (ORCHESTRATOR):
|
|
138
|
-
1. Re-read gennady source
|
|
165
|
+
1. Re-read the gennady LintCommand source in the checkout you resolved above.
|
|
139
166
|
2. Check command output tokens in the LintCommand implementation.
|
|
140
167
|
3. Update this script's parsing to match new tokens.
|
|
141
168
|
4. Until resolved → treat as a HARD blocker; DO NOT assume PASS.
|
|
@@ -174,7 +174,7 @@ run_cmd() {
|
|
|
174
174
|
while IFS=: read cls name; do
|
|
175
175
|
case "$cls" in
|
|
176
176
|
typecheck) run_cmd "typecheck" "npm run $name" npm run "$name" || true ;;
|
|
177
|
-
gennady) run_cmd "gennady DBC lint" "gennady lint ${#FILES[@]} files"
|
|
177
|
+
gennady) run_cmd "gennady DBC lint" "gennady lint ${#FILES[@]} files" "$SCRIPT_DIR/lint-artifacts.sh" "${FILES[@]}" || true ;;
|
|
178
178
|
lint) run_cmd "lint" "npm run $name" npm run "$name" || true ;;
|
|
179
179
|
test) run_cmd "test" "npm run $name" npm run "$name" || true ;;
|
|
180
180
|
format) run_cmd "format check" "npm run $name" npm run "$name" || true ;;
|
|
@@ -125,12 +125,13 @@ Per-task phase tokens:
|
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
b. Branch on phase status:
|
|
128
|
-
- `BLOCKED`
|
|
128
|
+
- `BLOCKED` on a repo-wide gate failing in files this ticket does not own → PARK the lane, do not fail it. Parallel lanes share one working tree, so that is usually a sibling lane mid-flight. Resume each parked lane once after the sub-batch drains; still blocked → `✋ AWAITING UNBLOCK`, not `❌ FAILED`.
|
|
129
|
+
- `BLOCKED` or `FAIL` otherwise → STOP this task's lane; mark task FAILED for the batch. Other parallel tasks in same sub-batch continue.
|
|
129
130
|
- `DONE` → record Handoff (artifacts, decisions, open). Continue to next phase.
|
|
130
131
|
|
|
131
132
|
c. Thread next phase's Inputs from this phase's Handoff (verbatim).
|
|
132
133
|
|
|
133
|
-
After all phases DONE: close Round (append `#### Round close` block
|
|
134
|
+
After all phases DONE: close Round (append `#### Round close` block per `ROUND_CLOSE_FORMAT`: a single `DONE` line). Sync trackers. Ticket Status → `[x] DONE`.
|
|
134
135
|
|
|
135
136
|
d. Dispatch AUDIT subagent (`subagent_type: general-purpose`, **`model: "haiku"`** — audit is mechanical verification + fact-checking, haiku sufficient and cheaper). MANDATORY, always runs. Include in prompt the SDD tooling location: `~/Developer/gennady/ai/skills/sdd-execute/scripts/sdd` (audit may use `lint`, `verify`, `check-blockers` subcommands):
|
|
136
137
|
```
|
|
@@ -161,7 +162,7 @@ Per-task phase tokens:
|
|
|
161
162
|
On BLOCKED/FAIL → STOP this task's lane; mark task FAILED.
|
|
162
163
|
After all fix phases DONE → close Round → dispatch AUDIT (round 2, fresh context). Branch again:
|
|
163
164
|
PASS → task complete.
|
|
164
|
-
FAIL (audit_attempt = 2) → STOP lane;
|
|
165
|
+
FAIL (audit_attempt = 2) → STOP lane; cap exhausted. Meta Status `[!] BLOCKED`, `🛑 BLOCKED: audit-cap-exhausted` with `💬 unblock: /sdd-execute <TSK-NN> --new-audit-session`. Report as `🛑 cap-exhausted`, distinct from `❌ FAILED`. The batch never lifts the cap itself.
|
|
165
166
|
|
|
166
167
|
— Wait for all parallel task lanes in sub-batch to finish.
|
|
167
168
|
— Any task FAILED → continue batch (other layers may not depend on it; if they do they'll be marked `⏸️ waiting`). Operator gets failure list in final summary.
|
|
@@ -202,7 +203,8 @@ Per-task phase tokens:
|
|
|
202
203
|
- Writing code or phase blocks in Execution Log. (Phase subagents do.)
|
|
203
204
|
- Sharing context between phase subagents of different tasks. Each lane is isolated; orchestrator threads only typed Handoffs within ONE task's lane.
|
|
204
205
|
- Skipping audit after Round close in default (per-task) mode. Audit dispatch is mandatory.
|
|
205
|
-
- Audit retry beyond 2
|
|
206
|
+
- Audit retry beyond 2 attempts per task per session. Hard cap; only the operator's literal `--new-audit-session` arg resets it, and only via the sibling `sdd-execute` skill.
|
|
207
|
+
- Writing a `✅ RESOLVED` marker for a blocker that is not resolved, to get past the `check-blockers` preflight. The marker records a fact, not permission.
|
|
206
208
|
- Re-running phases not flagged in `phases_to_fix`. The map finding-location → phase is the contract.
|
|
207
209
|
- Parallel dispatch ACROSS layers. Layers run sequentially.
|
|
208
210
|
- Parallel dispatch ACROSS sub-batches in same layer. Sub-batches exist exactly because of file conflicts.
|
package/dist/gennady.js
CHANGED
|
@@ -3,7 +3,7 @@ import { c as s } from "./chunks/shared-DbIfVgIg.js";
|
|
|
3
3
|
import "node:fs";
|
|
4
4
|
import "node:path";
|
|
5
5
|
import "node:url";
|
|
6
|
-
const r = "0.8.4-next.
|
|
6
|
+
const r = "0.8.4-next.6", i = /* @__PURE__ */ new Set(["help", "--help", "-h"]), p = /* @__PURE__ */ new Set(["--version", "-v"]), t = process.argv[2];
|
|
7
7
|
p.has(t) && (console.log(r), process.exit(0));
|
|
8
8
|
(!t || i.has(t)) && (await import("./chunks/help.cmd-Bz1YqU2V.js"), process.exit(0));
|
|
9
9
|
s({ name: "gennady", version: r });
|
package/package.json
CHANGED