fix: move the QA report-dir fallback into the shared exploratory section (review-army.md stays at its no-loss pin); classify qa-reports for gstack-clean

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-11-03 00:04:10 +00:00
1 parent d29af566af
commit 9b384c86dd
8 files changed
+13 -16

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
### Fixed
- **/ship's exploratory QA no longer writes reports into your project (GST-969).** Step 9.2.1 used to put its QA reports, deadline file and captured evidence in `.gstack/qa-reports/` inside the repository under review. In a project that doesn't ignore `.gstack/`, those files showed up as untracked changes, so they could land in the PR and change the tree the review certified. The new `gstack-qa-evidence report-dir` command creates a fresh run directory under `.gstack/tmp/qa-reports/` and adds `/.gstack/tmp/` to the repository's git exclude file, and /ship's QA step now uses it. It refuses a symlinked `.gstack`, a `.gitignore` rule that un-ignores `.gstack/tmp`, and a directory outside a git repository. /review's Step 4.7 still uses `.gstack/qa-reports`: /review's prompt is already at its size cap.
- **/ship and /review exploratory QA no longer write reports into your project (GST-969).** /ship Step 9.2.1 and /review Step 4.7 used to put its QA reports, deadline file and captured evidence in `.gstack/qa-reports/` inside the repository under review. In a project that doesn't ignore `.gstack/`, those files showed up as untracked changes, so they could land in the PR and change the tree the review certified. The new `gstack-qa-evidence report-dir` command creates a fresh run directory under `.gstack/tmp/qa-reports/` and adds `/.gstack/tmp/` to the repository's git exclude file, and both QA steps now use it. It refuses a symlinked `.gstack`, a `.gitignore` rule that un-ignores `.gstack/tmp`, and a directory outside a git repository. /qa keeps its `.gstack/qa-reports` output directory.
+2 -2
View File
@@ -388,7 +388,7 @@ function materialize(root: string, source: string) {
next: `Include every reportLinks entry in the Markdown report, and report the overall status as ${verdict.status}${verdict.open.length ? ` (open: ${verdict.open.join('; ')})` : ''}; this verdict is final. Next, write the Markdown report beside evidence.json.` };
}
/** A fresh report root for /review and /ship QA under the repository's git-excluded .gstack/tmp, so probes never change the reviewed tree. */
/** A fresh report root for caller QA (/review and /ship) under the repository's git-excluded .gstack/tmp, so probes never change the reviewed tree. */
function reportDir(): Record<string, any> {
const top = spawnSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8', timeout: 5000 });
if (top.status !== 0 || !top.stdout.trim()) throw new QaEvidenceError('report-dir needs a git work tree');
@@ -418,7 +418,7 @@ function reportDir(): Record<string, any> {
}
}
const QA_EVIDENCE_USAGE = 'report-dir (prints a fresh git-excluded report root for /ship QA) | capture ROOT ID [--public] --deadline FILE|--timeout-ms MS [--after PREVIOUS_CAPTURE --hypothesis TEXT] -- COMMAND ARGS (--after publishes checkpoint ID linking PREVIOUS_CAPTURE to this probe; required after the first complete capture unless a checkpoint was published) | checkpoint ROOT ID CAPTURE OBSERVATION_COMMAND HYPOTHESIS NEXT_COMMAND | checkpoint ROOT ID INTENT_FILE | materialize ROOT ANNOTATIONS (annotations: {evidence: [{capture, command, contract, expected, classification: pass|superseded|product-defect|fail|setup-blocked|blocked|inconclusive}], limits: [..]}; revision, runtime, cwd and learning are filled in)';
const QA_EVIDENCE_USAGE = 'report-dir (prints a fresh git-excluded report root for /review and /ship QA) | capture ROOT ID [--public] --deadline FILE|--timeout-ms MS [--after PREVIOUS_CAPTURE --hypothesis TEXT] -- COMMAND ARGS (--after publishes checkpoint ID linking PREVIOUS_CAPTURE to this probe; required after the first complete capture unless a checkpoint was published) | checkpoint ROOT ID CAPTURE OBSERVATION_COMMAND HYPOTHESIS NEXT_COMMAND | checkpoint ROOT ID INTENT_FILE | materialize ROOT ANNOTATIONS (annotations: {evidence: [{capture, command, contract, expected, classification: pass|superseded|product-defect|fail|setup-blocked|blocked|inconclusive}], limits: [..]}; revision, runtime, cwd and learning are filled in)';
export async function qaEvidenceMain(args: string[]): Promise<number> {
return withQaReceiptOutput(false, 'qa-evidence-receipt', value => value.event === 'observation'
+1 -1
View File
@@ -19,7 +19,7 @@ Missing or unreadable assets, prerequisites or permission block affected probes,
## 1. Charter and preflight
Reuse resolved REPORT_DIR; otherwise own a fresh `.gstack/qa-reports` subdirectory.
Reuse resolved REPORT_DIR; otherwise use the `reportDir` `EVIDENCE_TOOL report-dir` prints.
Write a **charter** per behavior: contract, risk, entrypoint, isolation, exit condition, source, commands and inputs. Save charters as Markdown in the report.
For /review and /ship, no plan/server is required.
+2 -2
View File
@@ -66,7 +66,7 @@ full contents. If either required Read is missing, complete it now before Charte
## 1. Charter and preflight
Reuse resolved REPORT_DIR; otherwise own a fresh \`.gstack/qa-reports\` subdirectory.
${reportOnly ? 'Reuse resolved REPORT_DIR; otherwise own a fresh \`.gstack/qa-reports\` subdirectory.' : 'Reuse resolved REPORT_DIR; otherwise use the \`reportDir\` \`EVIDENCE_TOOL report-dir\` prints.'}
Write a **charter** per behavior: contract, risk, entrypoint, isolation, exit condition, source, commands and inputs. Save charters as Markdown in the report.
${reportOnly ? '' : `For /review and /ship, no plan/server is required.
@@ -244,7 +244,7 @@ export function generateQAReview(ctx: TemplateContext): string {
Only the parent runs report-only discovery.
Never overwrite another run's reports. Batch only independent Reads.
${ship ? `Without a caller REPORT_DIR, set it to the \`reportDir\` that \`${quoteSafePath(ctx.paths.binDir)}/gstack-qa-evidence report-dir\` prints: git-excluded, so QA never changes the reviewed tree.\n` : ''}
${ship ? `**1. Load methods before any QA or explicit-verification probe.**
${generateQAReviewPreflight(ctx)}` : `**1. Set the charter and isolation.**
-1
View File
@@ -594,7 +594,6 @@ If the Red Team fails or times out, confirm it stopped and record its review as
Only the parent runs report-only discovery.
Never overwrite another run's reports. Batch only independent Reads.
Without a caller REPORT_DIR, set it to the `reportDir` that `$HOME/.claude/skills/gstack/bin/gstack-qa-evidence report-dir` prints: git-excluded, so QA never changes the reviewed tree.
**1. Load methods before any QA or explicit-verification probe.**
+1
View File
@@ -42,6 +42,7 @@ const KEPT: Record<string, string> = {
'review-*': '/ship review loop records',
'fix-check-*': '/ship fix-check inputs',
'tasks-*': 'gstack-tasks rows files (bin/gstack-tasks)',
'qa-reports': 'caller QA report runs from gstack-qa-evidence report-dir (lib/qa-evidence.ts)',
'autoplan': 'autoplan phase artifacts (autoplan/SKILL.md.tmpl)',
'eval-bisect': '/investigate eval bisect cache',
'price-gate': 'gstack-eval-plan price receipts',
+6 -5
View File
@@ -95,9 +95,10 @@ test('report-dir refuses outside a git work tree instead of guessing a location'
expect(fs.readdirSync(outside)).toEqual([]);
});
test('/ship Step 9.2.1 resolves REPORT_DIR through report-dir, not the product tree', () => {
const ctx: TemplateContext = { host: 'claude', skillName: 'ship', tmplPath: '', paths: HOST_PATHS.claude };
expect(RESOLVERS.QA_REVIEW(ctx)).toContain('set it to the `reportDir` that `$HOME/.claude/skills/gstack/bin/gstack-qa-evidence report-dir` prints: git-excluded');
const rendered = fs.readFileSync(path.resolve(import.meta.dir, '../ship/sections/review-army.md'), 'utf8');
expect(rendered).toContain('gstack-qa-evidence report-dir` prints: git-excluded');
test('caller QA without a resolved REPORT_DIR (/review, /ship) falls back to report-dir, not the product tree', () => {
const line = 'Reuse resolved REPORT_DIR; otherwise use the `reportDir` `EVIDENCE_TOOL report-dir` prints.';
const ctx: TemplateContext = { host: 'claude', skillName: 'qa', tmplPath: '', paths: HOST_PATHS.claude };
expect(RESOLVERS.QA_EXPLORATORY(ctx)).toContain(line);
expect(fs.readFileSync(path.resolve(import.meta.dir, '../qa/sections/exploratory.md'), 'utf8')).toContain(line);
expect(fs.readFileSync(path.resolve(import.meta.dir, '../qa/SKILL.md.tmpl'), 'utf8')).toContain('Use the directory as `REPORT_DIR`');
});
@@ -1,4 +0,0 @@
{
"bytes": 185,
"why": "GST-969: /ship Step 9.2.1 QA writes its reports under gstack-qa-evidence report-dir (git-excluded .gstack/tmp), not the product tree"
}