Repository navigation
Hybrid Signpost Generation (Cycle 0028) #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2863d3f
e0ede9b
2b074e4
fd8942a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| --- | ||
| title: "Hybrid Signpost Generation" | ||
| legend: PROCESS | ||
| --- | ||
|
|
||
| # Hybrid Signpost Generation | ||
|
|
||
| Source: dogfood discussion — fully-generated signposts lose hand-authored | ||
| prose; fully-manual signposts drift from code. | ||
| Legend: PROCESS | ||
|
|
||
| ## Sponsors | ||
|
|
||
| - Human: Process Steward | ||
| - Agent: Signpost Generator | ||
|
|
||
| ## Hill | ||
|
|
||
| Replace the fully-generated signpost approach with a hybrid model. | ||
| Hand-authored prose stays. Sections marked with | ||
| `<!-- generate:NAME -->` / `<!-- /generate -->` get their content | ||
| replaced on ship sync. | ||
|
|
||
| ## Playback Questions | ||
|
|
||
| ### Human | ||
|
|
||
| - [ ] Ship sync replaces content between generate markers without | ||
| touching surrounding prose. | ||
| - [ ] CLI.md, MCP.md, and GUIDE.md use generate markers for their | ||
| reference sections. | ||
| - [ ] Hand-written content outside markers survives regeneration. | ||
|
|
||
| ### Agent | ||
|
|
||
| - [ ] A `replaceGeneratedSections` function finds marker pairs and | ||
| replaces their content using a registry of named generators. | ||
| - [ ] Generators exist for `cli-commands`, `mcp-tools`, and | ||
| `signpost-inventory`. | ||
| - [ ] Tests prove markers are respected and content outside is | ||
| preserved. | ||
|
|
||
| ## Accessibility and Assistive Reading | ||
|
|
||
| Not in scope. | ||
|
|
||
| ## Localization and Directionality | ||
|
|
||
| Not in scope. | ||
|
|
||
| ## Agent Inspectability and Explainability | ||
|
|
||
| Generate markers are HTML comments — invisible in rendered markdown | ||
| but visible in source, making the contract explicit. | ||
|
|
||
| ## Non-goals | ||
|
|
||
| - [ ] Generating ARCHITECTURE.md content (stays fully hand-authored). | ||
| - [ ] Generating README.md content. | ||
| - [ ] Nested or conditional generation markers. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| --- | ||
| title: "Hybrid Signpost Generation" | ||
| outcome: hill-met | ||
| drift_check: yes | ||
| --- | ||
|
|
||
| # Hybrid Signpost Generation Retro | ||
|
|
||
| Design: `docs/design/0028-hybrid-signpost-generation/hybrid-signpost-generation.md` | ||
| Outcome: hill-met | ||
| Drift check: yes | ||
|
|
||
| ## Summary | ||
|
|
||
| Replaced fully-generated signposts with a hybrid model using | ||
| `<!-- generate:NAME -->` / `<!-- /generate -->` markers. Hand-authored | ||
| prose survives regeneration. Ship sync replaces only the content | ||
| between markers. Three generators implemented: cli-commands, | ||
| mcp-tools, signpost-inventory. | ||
|
|
||
| ## Playback Witness | ||
|
|
||
| - [Verification Witness](./witness/verification.md) | ||
|
|
||
| ## Drift | ||
|
|
||
| - None recorded. | ||
|
|
||
| ## New Debt | ||
|
|
||
| - `captureIdea` still does not write YAML frontmatter on new inbox items. | ||
|
|
||
| ## Cool Ideas | ||
|
|
||
| - Add a `generate:backlog-summary` section for GUIDE.md. | ||
| - Add marker support to ARCHITECTURE.md for auto-generated source listing. | ||
|
|
||
| ## Backlog Maintenance | ||
|
|
||
| - [x] Inbox processed | ||
| - [x] Priorities reviewed | ||
| - [x] Dead work buried or merged |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| --- | ||
| title: "Verification Witness for Cycle 28" | ||
| --- | ||
|
|
||
| # Verification Witness for Cycle 28 | ||
|
|
||
| This witness proves that `Hybrid Signpost Generation` now carries the required | ||
| behavior and adheres to the repo invariants. | ||
|
|
||
| ## Test Results | ||
|
|
||
| ``` | ||
| > method@0.2.0 test | ||
| > vitest run --config vitest.config.ts | ||
|
|
||
|
|
||
| RUN v4.1.2 ./method | ||
|
|
||
|
|
||
| Test Files 12 passed (12) | ||
| Tests 127 passed (127) | ||
| Start at 22:56:15 | ||
| Duration 987ms (transform 884ms, setup 0ms, import 2.85s, tests 1.05s, environment 1ms) | ||
| ``` | ||
|
|
||
| ## Drift Results | ||
|
|
||
| ``` | ||
| Playback-question drift found. | ||
| Scanned 1 active cycle, 6 playback questions, 140 test descriptions. | ||
| Search basis: exact normalized match in tests/**/*.test.* and tests/**/*.spec.* descriptions. | ||
|
|
||
| docs/design/0028-hybrid-signpost-generation/hybrid-signpost-generation.md | ||
| - Human: Ship sync replaces content between generate markers without touching surrounding prose. | ||
| Near miss: "replaces content between generate markers without touching surrounding prose" | ||
| - Human: CLI.md, MCP.md, and GUIDE.md use generate markers for their reference sections. | ||
| No exact normalized test description match found. | ||
| - Human: Hand-written content outside markers survives regeneration. | ||
| No exact normalized test description match found. | ||
| - Agent: A `replaceGeneratedSections` function finds marker pairs and replaces their content using a registry of named generators. | ||
| No exact normalized test description match found. | ||
| - Agent: Generators exist for `cli-commands`, `mcp-tools`, and `signpost-inventory`. | ||
| No exact normalized test description match found. | ||
| - Agent: Tests prove markers are respected and content outside is preserved. | ||
| No exact normalized test description match found. | ||
| ``` | ||
|
|
||
| ## Manual Verification | ||
|
|
||
| - [x] Automated capture completed successfully. |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,90 @@ | ||||||||||||
| import { CLI_TOPICS, usage } from './cli-args.js'; | ||||||||||||
| import { MCP_TOOLS } from './mcp.js'; | ||||||||||||
|
|
||||||||||||
| export type GeneratorFn = () => string; | ||||||||||||
| export type GeneratorRegistry = Record<string, GeneratorFn>; | ||||||||||||
|
|
||||||||||||
| const MARKER_PATTERN = /<!-- generate:(\S+) -->\n[\s\S]*?<!-- \/generate -->/gu; | ||||||||||||
|
|
||||||||||||
| export function replaceGeneratedSections( | ||||||||||||
| content: string, | ||||||||||||
| generators: GeneratorRegistry, | ||||||||||||
| ): string { | ||||||||||||
| return content.replace(MARKER_PATTERN, (match, name: string) => { | ||||||||||||
| const generator = generators[name]; | ||||||||||||
| if (generator === undefined) { | ||||||||||||
| return match; | ||||||||||||
| } | ||||||||||||
| const generated = generator(); | ||||||||||||
| return `<!-- generate:${name} -->\n${generated}<!-- /generate -->`; | ||||||||||||
|
Comment on lines
+18
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Fragile contract: relies on generators to include trailing newline. The output format assumes All three current generators happen to comply, but this is implicit. A future generator omitting the trailing newline will produce malformed output with no warning. 🛡️ Defensive normalization const generated = generator();
- return `<!-- generate:${name} -->\n${generated}<!-- /generate -->`;
+ const normalized = generated.endsWith('\n') ? generated : `${generated}\n`;
+ return `<!-- generate:${name} -->\n${normalized}<!-- /generate -->`;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
| }); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| export function cliCommandsGenerator(): string { | ||||||||||||
| const lines: string[] = []; | ||||||||||||
| for (const topic of CLI_TOPICS) { | ||||||||||||
| const text = usage(topic); | ||||||||||||
| const usageLine = text.split('\n')[0] ?? ''; | ||||||||||||
| const description = text.split('\n').slice(1).filter((l) => l.trim().length > 0); | ||||||||||||
| lines.push(`### \`${usageLine.replace(/^Usage: /u, '')}\``); | ||||||||||||
| lines.push(''); | ||||||||||||
| for (const line of description) { | ||||||||||||
| lines.push(line); | ||||||||||||
| } | ||||||||||||
| if (description.length > 0) { | ||||||||||||
| lines.push(''); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| lines.push('### Exit Codes'); | ||||||||||||
| lines.push(''); | ||||||||||||
| lines.push('| Code | Meaning |'); | ||||||||||||
| lines.push('|------|---------|'); | ||||||||||||
| lines.push('| 0 | Success |'); | ||||||||||||
| lines.push('| 1 | Error (bad arguments, missing workspace, failed operation) |'); | ||||||||||||
| lines.push('| 2 | Drift found (from `method drift`) |'); | ||||||||||||
|
Comment on lines
+38
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Exit codes table is hardcoded - potential drift risk. Exit code semantics are duplicated here rather than derived from a single source of truth. If Consider whether exit codes should be co-located with their definitions (e.g., constants in 🤖 Prompt for AI Agents |
||||||||||||
| lines.push(''); | ||||||||||||
| return lines.join('\n'); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| export function mcpToolsGenerator(): string { | ||||||||||||
| const lines: string[] = []; | ||||||||||||
| for (const tool of MCP_TOOLS) { | ||||||||||||
| lines.push(`### \`${tool.name}\``); | ||||||||||||
| lines.push(''); | ||||||||||||
| lines.push(tool.description); | ||||||||||||
| lines.push(''); | ||||||||||||
|
|
||||||||||||
| const params = Object.entries(tool.inputSchema.properties) | ||||||||||||
| .filter(([key]) => key !== 'workspace'); | ||||||||||||
| const required = new Set(tool.inputSchema.required.filter((k) => k !== 'workspace')); | ||||||||||||
|
|
||||||||||||
| if (params.length > 0) { | ||||||||||||
| lines.push('**Parameters:**'); | ||||||||||||
| lines.push(''); | ||||||||||||
| for (const [key, schema] of params) { | ||||||||||||
| const req = required.has(key) ? '(required)' : '(optional)'; | ||||||||||||
| const desc = ('description' in schema && schema.description) ? ` — ${schema.description}` : ''; | ||||||||||||
| const enumValues = ('enum' in schema && schema.enum) ? ` (${schema.enum.join(', ')})` : ''; | ||||||||||||
| lines.push(`- \`${key}\` ${req} \`${schema.type}\`${enumValues}${desc}`); | ||||||||||||
| } | ||||||||||||
| lines.push(''); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| return lines.join('\n'); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| export function signpostInventoryGenerator(): string { | ||||||||||||
| const lines: string[] = [ | ||||||||||||
| '| Signpost | Type | Description |', | ||||||||||||
| '|----------|------|-------------|', | ||||||||||||
| '| `README.md` | Hand-authored | Core doctrine and filesystem shape. |', | ||||||||||||
| '| `ARCHITECTURE.md` | Hand-authored | How the source code is organized. |', | ||||||||||||
| '| `docs/BEARING.md` | Generated | Current direction and recent ships. |', | ||||||||||||
| '| `docs/VISION.md` | Generated | Bounded executive synthesis. |', | ||||||||||||
| '| `docs/CLI.md` | Generated | CLI command reference. |', | ||||||||||||
| '| `docs/MCP.md` | Generated | MCP tool reference. |', | ||||||||||||
| '| `docs/GUIDE.md` | Hybrid | Operator advice with generated sections. |', | ||||||||||||
| '', | ||||||||||||
| ]; | ||||||||||||
| return lines.join('\n'); | ||||||||||||
| } | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regex silently fails on CRLF line endings.
The pattern mandates a literal
\nafter the opening marker. Files with Windows-style\r\nline endings will never match, causing silent no-op behavior. This is a landmine waiting for a contributor on Windows or a file that passes through a tool that normalizes to CRLF.🛠️ Proposed fix to handle both LF and CRLF
📝 Committable suggestion
🤖 Prompt for AI Agents