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:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-07 04:17:35 +00:00
1 parent 1970da59c4
commit 3742ee9527
88 files changed
+442 -255

No files matched your search

+10
View File
@@ -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;
+2 -2
View File
@@ -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) {
+6 -5
View File
@@ -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 };
+2 -1
View File
@@ -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);
+5
View File
@@ -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}`;
+2 -2
View 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} |
\\\`\\\`\\\`
+2 -2
View File
@@ -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;