mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-07 17:28:23 +01:00
feat: cut standalone /plan-eng-review; /autoplan keeps its eng phase (GST-1698) (#4108)
* feat: cut standalone /plan-eng-review; /autoplan keeps its eng phase (GST-1698) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: lower shrunk SKILL.md size pins; keep the retired methodology's pins measured beside /autoplan Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: carry /autoplan's plan-eng-review.md into Kiro installs Kiro's per-skill loop copied only SKILL.md and sections, so ~/.kiro/skills/gstack-autoplan lacked the Phase 3 methodology file its SKILL.md reads. Generated sibling *.md files now travel with SKILL.md under the same ownership guards. Review-Finding: p1/specialist-batched#1 reader=red-team Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: file /plan-eng-review retirement follow-ups; scope the changelog claim to ./setup Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: route architecture-review requests to /autoplan by its description (journey-plan-eng) 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>
This commit is contained in:
1 parent
1970da59c4
commit
3742ee9527
88 files changed
+442
-255
No files matched your search
@@ -9,6 +9,16 @@ import type { HostConfig } from './host-config';
|
||||
|
||||
const SKIP = new Set(['node_modules', '.git', 'dist']);
|
||||
|
||||
/**
|
||||
* Retired skills: the source stays because another skill reads it, but no host
|
||||
* registers it. plan-eng-review is /autoplan's eng-phase methodology (GST-1698).
|
||||
* External hosts get that methodology beside gstack-autoplan/SKILL.md as
|
||||
* <name>.md, since Codex and Copilot register every nested SKILL.md. setup and
|
||||
* gstack-relink read the same list from GSTACK_RETIRED_SKILLS in
|
||||
* bin/gstack-install-registry.sh.
|
||||
*/
|
||||
export const RETIRED_SKILLS = ['plan-eng-review'];
|
||||
|
||||
/** The generator and its coverage checks use the same include-minus-skip rule. */
|
||||
export function includesSkill(host: HostConfig, skillDir: string): boolean {
|
||||
const { includeSkills, skipSkills } = host.generation;
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { mkdirpSync } from '../lib/fs-utils';
|
||||
import { discoverTemplates, includesSkill } from './discover-skills';
|
||||
import { discoverTemplates, includesSkill, RETIRED_SKILLS } from './discover-skills';
|
||||
import claudeHost from '../hosts/claude';
|
||||
import { COMMAND_DESCRIPTIONS as BROWSE_COMMANDS } from '../browse/src/commands';
|
||||
|
||||
@@ -145,7 +145,7 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise<Gener
|
||||
const skills: SkillEntry[] = [];
|
||||
for (const t of templates) {
|
||||
if (!includesSkill(claudeHost, path.dirname(t.tmpl))) continue;
|
||||
if (opts.excludedSkills?.includes(path.dirname(t.tmpl))) continue;
|
||||
if (opts.excludedSkills?.includes(path.dirname(t.tmpl)) || RETIRED_SKILLS.includes(path.dirname(t.tmpl))) continue;
|
||||
const filePath = path.join(root, t.tmpl);
|
||||
const entry = parseSkillFrontmatter(filePath);
|
||||
if (!entry) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* Used by skill:check and CI freshness checks.
|
||||
*/
|
||||
|
||||
import { discoverTemplates, discoverSectionTemplates, includesSkill } from './discover-skills';
|
||||
import { discoverTemplates, discoverSectionTemplates, includesSkill, RETIRED_SKILLS } from './discover-skills';
|
||||
import { externalSkillName, extractNameAndDescription } from './external-skill-names';
|
||||
export { extractNameAndDescription } from './external-skill-names';
|
||||
import { generateLlmsTxt } from './gen-llms-txt';
|
||||
@@ -755,9 +755,10 @@ function processExternalHost(
|
||||
const hostConfig = getHostConfig(host);
|
||||
|
||||
const name = externalSkillName(skillDir === '.' ? '' : skillDir, frontmatterName);
|
||||
const retired = RETIRED_SKILLS.includes(skillDir);
|
||||
// --out-dir mirrors the host tree (outputs only; inputs read from ROOT).
|
||||
const outputDir = path.join(options.outputRoot, hostConfig.hostSubdir, 'skills', name);
|
||||
const outputPath = path.join(outputDir, 'SKILL.md');
|
||||
const outputDir = path.join(options.outputRoot, hostConfig.hostSubdir, 'skills', retired ? 'gstack-autoplan' : name);
|
||||
const outputPath = path.join(outputDir, retired ? `${skillDir}.md` : 'SKILL.md');
|
||||
|
||||
// Guard against symlink loops
|
||||
let symlinkLoop = false;
|
||||
@@ -790,7 +791,7 @@ function processExternalHost(
|
||||
|
||||
// Config-driven: generate metadata (e.g., openai.yaml for Codex). The
|
||||
// default_prompt comes from the template's codex_default_prompt: key when set.
|
||||
const metadata = hostConfig.generation.generateMetadata && !symlinkLoop ? {
|
||||
const metadata = hostConfig.generation.generateMetadata && !symlinkLoop && !retired ? {
|
||||
outputPath: path.join(outputDir, 'agents', 'openai.yaml'),
|
||||
content: generateOpenAIYaml(name, condenseOpenAIShortDescription(extractedDescription), extractCodexDefaultPrompt(tmplContent)),
|
||||
} : undefined;
|
||||
@@ -884,7 +885,7 @@ function processTemplate(tmplPath: string, host: Host, options: RenderOptions):
|
||||
if (skillDir === '' || skillDir === '.') content = omitDisabledSkills(content, [...options.disabledSkills, ...excludedSkillDirs(options.modules)]);
|
||||
content = rewriteInstallRoot(insertRuntimePreludes(rewriteCarvedSectionRefs(content, ctx), ctx), currentHostConfig, options.installRoot);
|
||||
|
||||
const preamble = hoistsPreamble(ctx) && tmplContent.includes('{{PREAMBLE}}') && !effectiveSuppressedResolvers(currentHostConfig, options).has('PREAMBLE')
|
||||
const preamble = !(host !== 'claude' && RETIRED_SKILLS.includes(skillDir)) && hoistsPreamble(ctx) && tmplContent.includes('{{PREAMBLE}}') && !effectiveSuppressedResolvers(currentHostConfig, options).has('PREAMBLE')
|
||||
? { outputPath: path.join(path.dirname(outputPath), 'sections', SHARED_PREAMBLE_FILE), content: renderSharedPreamble(ctx, currentHostConfig, options) }
|
||||
: undefined;
|
||||
return { outputPath, content, symlinkLoop, metadata, preamble };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { discoverTemplates, includesSkill } from './discover-skills';
|
||||
import { discoverTemplates, includesSkill, RETIRED_SKILLS } from './discover-skills';
|
||||
import { externalSkillName, extractNameAndDescription } from './external-skill-names';
|
||||
import { getHostConfig } from '../hosts';
|
||||
import { CLAUDE_CODE_RUNTIME_FILES } from '../lib/claude-code-migration';
|
||||
@@ -114,6 +114,7 @@ const sourceNames = new Set<string>();
|
||||
for (const template of discoverTemplates(source)) {
|
||||
const dir = path.dirname(template.tmpl);
|
||||
const name = externalSkillName(dir, extractNameAndDescription(fs.readFileSync(path.join(source, template.tmpl), 'utf8')).name);
|
||||
if (RETIRED_SKILLS.includes(dir)) continue;
|
||||
sourceNames.add(name);
|
||||
if (!includesSkill(config, dir)) continue;
|
||||
names.add(name);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { toShellPath, type TemplateContext } from './types';
|
||||
import * as path from 'path';
|
||||
import { getHostConfig } from '../../hosts';
|
||||
import { runtimeRootPrelude } from './runtime-root';
|
||||
import { RETIRED_SKILLS } from '../discover-skills';
|
||||
|
||||
/** Claude's scoped hook enforces the parent publication boundary during /autoplan. */
|
||||
export function generateAutoplanPublicationHook(ctx: TemplateContext, args?: string[]): string {
|
||||
@@ -119,6 +120,10 @@ export function generateAutoplanReviewFile(ctx: TemplateContext, args?: string[]
|
||||
if (ctx.host === 'claude') return `\`${ctx.paths.skillRoot}/${skill}/SKILL.md\``;
|
||||
|
||||
const host = getHostConfig(ctx.host);
|
||||
if (RETIRED_SKILLS.includes(skill)) {
|
||||
const own = `gstack-autoplan/${skill}.md`;
|
||||
return `the file \`${skill}.md\` in the installed \`/autoplan\` SKILL.md directory (local: \`${path.posix.dirname(host.localSkillRoot)}/${own}\`; global: \`~/${path.posix.dirname(host.globalRoot)}/${own}\`${ctx.host === 'codex' ? ', or the corresponding skills directory under CODEX_HOME when configured' : ''})`;
|
||||
}
|
||||
const file = `gstack-${skill}/SKILL.md`;
|
||||
const local = `${path.posix.dirname(host.localSkillRoot)}/${file}`;
|
||||
const global = `~/${path.posix.dirname(host.globalRoot)}/${file}`;
|
||||
|
||||
@@ -58,7 +58,7 @@ and outside_status; retain partial coverage. These details do not clear Eng Revi
|
||||
|
||||
**3. Choose the historical verdict.** CLEARED requires the selected Eng Review
|
||||
to be \`clean\`, within 7 days and fresh under step 2. Otherwise report NOT CLEARED
|
||||
and its missing, stale or open-issue reason.${ctx.skillName === 'ship' ? ` With no Eng Review record, say "no saved Eng Review: run the \`PERSIST:\` line a read-only /plan-eng-review printed, or rerun it" (docs/troubleshooting.md#plan-eng-review-not-persisted).` : ''} If \`skip_eng_review\` is true, show
|
||||
and its missing, stale or open-issue reason.${ctx.skillName === 'ship' ? ` With no Eng Review record, say "no saved Eng Review: run the \`PERSIST:\` line a read-only eng review printed, or rerun /autoplan" (docs/troubleshooting.md#plan-eng-review-not-persisted).` : ''} If \`skip_eng_review\` is true, show
|
||||
"SKIPPED (global)" for Eng Review and CLEARED for this dashboard.
|
||||
${ctx.skillName === 'ship' ? 'This verdict never skips Step 9 or its finding, approval and convergence gates. Continue Step 1 even when history is NOT CLEARED.' : 'Eng Review is required by default; `gstack-config set skip_eng_review true` disables that requirement.'}
|
||||
|
||||
@@ -164,7 +164,7 @@ ${conditionalWrites ? 'Display `clean` as CLEAR and `issues_open` as ISSUES OPEN
|
||||
|--------|---------|-----|------|--------|----------|
|
||||
| CEO Review | \\\`/plan-ceo-review\\\` | Scope & strategy | {runs} | {status} | {findings} |
|
||||
| Outside Review | {recorded provider and trigger} | Independent 2nd opinion | {runs} | {outside_status} | {findings} |
|
||||
| Eng Review | \\\`/plan-eng-review\\\` | Architecture & tests (required) | {runs} | {status} | {findings} |
|
||||
| Eng Review | \\\`${ctx.skillName === 'plan-eng-review' ? '/plan-eng-review' : '/autoplan'}\\\` | Architecture & tests (required) | {runs} | {status} | {findings} |
|
||||
| Design Review | \\\`/plan-design-review\\\` | UI/UX gaps | {runs} | {status} | {findings} |
|
||||
| DX Review | \\\`/plan-devex-review\\\` | Developer experience gaps | {runs} | {status} | {findings} |
|
||||
\\\`\\\`\\\`
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { TemplateContext } from './types';
|
||||
import { discoverTemplates, includesSkill } from '../discover-skills';
|
||||
import { discoverTemplates, includesSkill, RETIRED_SKILLS } from '../discover-skills';
|
||||
import { getHostConfig } from '../../hosts/index';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..', '..');
|
||||
@@ -51,7 +51,7 @@ export function generateVoiceTriggerRouting(ctx: TemplateContext): string {
|
||||
// Sorted so the render doesn't depend on filesystem iteration order.
|
||||
const skillDirs = discoverTemplates(ROOT).map(t => path.dirname(t.tmpl)).sort();
|
||||
for (const skillDir of skillDirs) {
|
||||
if (skillDir === '.' || !includesSkill(hostConfig, skillDir)) continue;
|
||||
if (skillDir === '.' || RETIRED_SKILLS.includes(skillDir) || !includesSkill(hostConfig, skillDir)) continue;
|
||||
const content = fs.readFileSync(path.join(ROOT, skillDir, 'SKILL.md.tmpl'), 'utf-8').replace(/\r\n/g, '\n');
|
||||
const triggers = extractVoiceTriggers(content);
|
||||
if (triggers.length === 0) continue;
|
||||
|
||||
Reference in new issue
Block a user