mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
* fix(capy): the install trailer says behind=<n> and its fix, so a stale install shows in every relayed trailer (field list 2 item 13, #2378) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(capy): a lease records its holder's start identity, so a pid reused after a wake is reclaimed; a skipped session-start upgrade ends fail behind, never ok (GBRA-105, GBRA-106) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(lease): stale needs proof (unreadable or malformed identities keep the lease), ps identity is timezone-independent, and reclaim never deletes a concurrent renewal Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(autoplan): enforced round cap and spend budget, deferred close, public export without machine paths, opt-in durable checkpoints (field list 2 W5-W7) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(lease): one directory lock for publish, prune and reclaim; reclaim rereads the record judged stale, so a renewal is never deleted or hidden Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(lease): the directory lock names its holder; broken only when the holder is dead or its pid reused, unlock removes only its own lock, touch exits 3 under a live holder Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(lease): breakers serialize on .lock.break and reread the lock before removing it; gstack-skill-start reports a lease the held lock kept out Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(lease): name the double-crash limit of breaking a dead lock Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(capy): --for review-voice install profile, no-Drive bootstrap command, reviewer prompt trailer (field list 2 W11) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> (cherry picked from commit89bf556c11) * test: pin review-voice row, helpers row and the no-Drive fallback in existing install tests Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> (cherry picked from commit7f6c70459a) * test: the DRIVER template names the reviewer trailer relay (pregate lane) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> (cherry picked from commit2545d9a6d2) * test(context-budget): capy skill eager ceiling for the review-voice subagent paragraph (W11; ratchet protocol, capture value) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(eval-plan): prereg diagnostics dry run (W8) and .gstack/eval-models.json lint (W9), WIP Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> (cherry picked from commitb509096218) * feat(eval-plan): W8/W9 tests, docs/eval-models.md, troubleshooting anchors, /eval-plan steps Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> (cherry picked from commit9335e45de2) * test(eval-plan): eval-plan/SKILL.md names only defined codes and accepted flags Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> (cherry picked from commita7a974d268) * test(review-voice): the launch line points at the verdict's bun-only directory (#3233's launch contract) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(third-party-actions): the Aside drive-offer detector matches the product name, not the English idiom (paid run 38988927989 tpa-absent-linux false positive) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(autoplan): export refuses artifact paths outside the run before writing; a deferred close keeps earlier undecided findings; reopening CEO grants Eng its rerun (review of #3234) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(autoplan-budget): bind the reservation expectation to lib/autoplan-budget's default estimate (test-of-test ratchet) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(evals): the docs observer accepts the spawned-prefixed gstack-skill-start --help read Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(windows): curate capy-review-voice out of the Windows subset (POSIX launcher contract) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: changelog entry for field list 2 wave A Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: stamp v1.91.115.0 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
88 lines
4.2 KiB
TypeScript
88 lines
4.2 KiB
TypeScript
/**
|
|
* The doctor's component table: one source (lib/doctor-components.ts), a
|
|
* committed Bash render the doctor sources without Bun
|
|
* (bin/gstack-doctor-components.sh) that must stay fresh, every skill
|
|
* directory classified, and every result code anchored in
|
|
* docs/troubleshooting.md through the same render.
|
|
*/
|
|
import { describe, expect, test } from 'bun:test';
|
|
import { spawnSync } from 'child_process';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import {
|
|
COMPONENT_IDS, CORE_IDS, DOCTOR_COMPONENTS, INSTALL_PROFILES, SKILL_REQUIREMENTS, renderDoctorComponentsSh,
|
|
} from '../lib/doctor-components';
|
|
import { RESULT_CODES } from '../lib/result-codes';
|
|
import { DOCTOR_COMPONENTS_RELPATH } from '../scripts/gen-doctor-components';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
const bash = (script: string) => spawnSync('bash', ['-c', `. "${path.join(ROOT, DOCTOR_COMPONENTS_RELPATH)}" && ${script}`], { encoding: 'utf8', timeout: 20_000 });
|
|
|
|
describe('doctor component table', () => {
|
|
test('the committed Bash render is fresh (matches the generator)', () => {
|
|
expect(fs.readFileSync(path.join(ROOT, DOCTOR_COMPONENTS_RELPATH), 'utf8')).toBe(renderDoctorComponentsSh());
|
|
});
|
|
|
|
test('ids are unique kebab-case and every skill requirement names known ids', () => {
|
|
expect(new Set(COMPONENT_IDS).size).toBe(COMPONENT_IDS.length);
|
|
for (const id of COMPONENT_IDS) expect(id).toMatch(/^[a-z][a-z-]*$/);
|
|
for (const [skill, req] of Object.entries(SKILL_REQUIREMENTS)) {
|
|
for (const id of [...req.requires, ...req.optional]) expect(COMPONENT_IDS, `${skill} -> ${id}`).toContain(id);
|
|
for (const id of req.requires) expect(CORE_IDS, `${skill} lists core row ${id}; core rows are implied`).not.toContain(id);
|
|
expect(req.requires.filter(id => req.optional.includes(id))).toEqual([]);
|
|
}
|
|
});
|
|
|
|
test('every skill directory in the repo is classified, and no classified skill is missing (install profiles are the only other rows)', () => {
|
|
const skills = fs.readdirSync(ROOT).filter(d => fs.existsSync(path.join(ROOT, d, 'SKILL.md.tmpl'))).sort();
|
|
expect(skills.length).toBeGreaterThan(40);
|
|
expect(Object.keys(SKILL_REQUIREMENTS).filter(s => !INSTALL_PROFILES.includes(s)).sort()).toEqual(skills);
|
|
expect(INSTALL_PROFILES).toEqual(['review-voice']);
|
|
for (const profile of INSTALL_PROFILES) {
|
|
expect(skills, `${profile} is an install profile, not a skill`).not.toContain(profile);
|
|
expect(SKILL_REQUIREMENTS[profile]?.requires).toEqual(['helpers']);
|
|
}
|
|
});
|
|
|
|
test('the Bash render answers the same questions as the table, and refuses unknown names', () => {
|
|
const r = bash([
|
|
'gstack_doctor_skill_requires autoplan; echo',
|
|
'gstack_doctor_skill_optional autoplan; echo',
|
|
'gstack_doctor_skill_requires qa; echo',
|
|
'gstack_doctor_component_kind codex-cli; echo',
|
|
'gstack_doctor_component_title pins; echo',
|
|
'gstack_result_anchor REVISION_UNMET; echo',
|
|
'gstack_doctor_skill_requires no-such-skill; echo "rc=$?"',
|
|
'gstack_doctor_component_kind nope; echo "rc=$?"',
|
|
'echo "$GSTACK_DOCTOR_CORE"',
|
|
].join('; '));
|
|
expect(r.status, r.stderr).toBe(0);
|
|
expect(r.stdout.split('\n')).toEqual([
|
|
SKILL_REQUIREMENTS.autoplan.requires.join(' '),
|
|
SKILL_REQUIREMENTS.autoplan.optional.join(' '),
|
|
SKILL_REQUIREMENTS.qa.requires.join(' '),
|
|
'optional',
|
|
'project pins',
|
|
RESULT_CODES.REVISION_UNMET.anchor,
|
|
'rc=1',
|
|
'rc=1',
|
|
CORE_IDS.join(' '),
|
|
'',
|
|
]);
|
|
});
|
|
|
|
test('the render is Bash 3.2 syntax: no arrays, no [[ ]], no ${var//}', () => {
|
|
const text = renderDoctorComponentsSh();
|
|
expect(text).not.toMatch(/\[\[|\]\]|\$\{[^}]*\/\/|=\(/);
|
|
for (const c of DOCTOR_COMPONENTS) expect(text).toContain(`${c.id}) printf '%s' '${c.kind}'`);
|
|
});
|
|
|
|
test('the Astra-patch and browser rows are the installer contract', () => {
|
|
expect(SKILL_REQUIREMENTS.autoplan.requires).toEqual(['claude', 'codex', 'patch']);
|
|
expect(SKILL_REQUIREMENTS.autoplan.optional).toContain('codex-cli');
|
|
expect(SKILL_REQUIREMENTS.browse.requires).toContain('browser');
|
|
expect(SKILL_REQUIREMENTS.codex.requires).toContain('codex-cli');
|
|
expect(SKILL_REQUIREMENTS.cso.requires).toContain('cso');
|
|
});
|
|
});
|