avtc-pi-featyard 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +194 -0
- package/agents/fy-design-reviewer.md +77 -0
- package/agents/fy-feature-verifier.md +24 -0
- package/agents/fy-general-reviewer.md +81 -0
- package/agents/fy-guidelines-reviewer.md +46 -0
- package/agents/fy-implementer.md +20 -0
- package/agents/fy-performance-reviewer.md +46 -0
- package/agents/fy-plan-reviewer.md +74 -0
- package/agents/fy-plan-verifier.md +44 -0
- package/agents/fy-quality-reviewer.md +50 -0
- package/agents/fy-requirements-reviewer.md +46 -0
- package/agents/fy-researcher.md +6 -0
- package/agents/fy-security-reviewer.md +46 -0
- package/agents/fy-task-verifier.md +42 -0
- package/agents/fy-testing-reviewer.md +60 -0
- package/assets/images/kanban-board.png +0 -0
- package/assets/images/settings-dropdown.png +0 -0
- package/assets/images/settings.png +0 -0
- package/assets/images/widget.png +0 -0
- package/docs/CONFIGURATION.md +266 -0
- package/index.ts +8 -0
- package/package.json +85 -0
- package/skills/fy-design/SKILL.md +108 -0
- package/skills/fy-design-review/SKILL.md +45 -0
- package/skills/fy-finish/SKILL.md +28 -0
- package/skills/fy-implement/SKILL.md +36 -0
- package/skills/fy-plan/SKILL.md +77 -0
- package/skills/fy-plan-review/SKILL.md +45 -0
- package/skills/fy-research/SKILL.md +38 -0
- package/skills/fy-review/SKILL.md +88 -0
- package/skills/fy-verify/SKILL.md +23 -0
- package/src/commands/auto-agent-commands.ts +748 -0
- package/src/commands/kanban-commands.ts +278 -0
- package/src/commands/workflow-commands.ts +531 -0
- package/src/compaction/compact-handler.ts +371 -0
- package/src/compaction/compact-message.ts +52 -0
- package/src/compaction/compact-trigger.ts +121 -0
- package/src/compaction/safe-editor-write.ts +18 -0
- package/src/events/agent/agent-end.ts +19 -0
- package/src/events/agent/agent-settled.ts +20 -0
- package/src/events/agent/agent-start.ts +18 -0
- package/src/events/agent/turn-end.ts +20 -0
- package/src/events/agent/turn-start.ts +19 -0
- package/src/events/event-deps.ts +33 -0
- package/src/events/index.ts +91 -0
- package/src/events/input/input.ts +143 -0
- package/src/events/session/context.ts +68 -0
- package/src/events/session/model-select.ts +20 -0
- package/src/events/session/session-compact.ts +20 -0
- package/src/events/session/session-shutdown.ts +26 -0
- package/src/events/session/session-start.ts +45 -0
- package/src/events/session/session-tree.ts +17 -0
- package/src/events/tool/tool-call.ts +58 -0
- package/src/events/tool/tool-result.ts +54 -0
- package/src/git/git-queries.ts +154 -0
- package/src/git/resolve-base-branch.ts +173 -0
- package/src/git/worktrees/worktree-helpers.ts +142 -0
- package/src/git/worktrees/worktree-interception.ts +66 -0
- package/src/git/worktrees/worktree-lifecycle.ts +380 -0
- package/src/guardrails/completion-gating.ts +63 -0
- package/src/guardrails/file-classifier.ts +461 -0
- package/src/guardrails/force-add-guard.ts +312 -0
- package/src/guardrails/guardrail-tracker.ts +73 -0
- package/src/guardrails/guardrail-violations.ts +47 -0
- package/src/guardrails/guardrails-engine.ts +663 -0
- package/src/guardrails/session-guardrails.ts +75 -0
- package/src/guardrails/shell-decompose.ts +285 -0
- package/src/guardrails/tdd-enforcement.ts +122 -0
- package/src/guardrails/test-output.ts +272 -0
- package/src/index.ts +598 -0
- package/src/integrations/parallel-work-guardrail-integration.ts +29 -0
- package/src/integrations/subagent-integration.ts +166 -0
- package/src/integrations/todo-integration.ts +58 -0
- package/src/kanban/auto-agent/auto-agent-cleanup.ts +37 -0
- package/src/kanban/auto-agent/auto-agent-events.ts +142 -0
- package/src/kanban/auto-agent/auto-agent-grace-period.ts +83 -0
- package/src/kanban/auto-agent/auto-agent-lifecycle.ts +497 -0
- package/src/kanban/auto-agent/auto-agent-notify.ts +43 -0
- package/src/kanban/auto-agent/auto-agent-pause.ts +44 -0
- package/src/kanban/auto-agent/auto-agent-state-machine.ts +629 -0
- package/src/kanban/data/kanban-database.ts +851 -0
- package/src/kanban/data/kanban-db-schema.ts +137 -0
- package/src/kanban/data/kanban-detect-project.ts +43 -0
- package/src/kanban/data/kanban-move-feature.ts +43 -0
- package/src/kanban/data/kanban-types.ts +64 -0
- package/src/kanban/ensure-feature.ts +226 -0
- package/src/kanban/kanban-board-ui/app.js +1125 -0
- package/src/kanban/kanban-board-ui/csv-parser.d.ts +5 -0
- package/src/kanban/kanban-board-ui/csv-parser.js +80 -0
- package/src/kanban/kanban-board-ui/index.html +127 -0
- package/src/kanban/kanban-board-ui/style.css +820 -0
- package/src/kanban/kanban-bridge.ts +216 -0
- package/src/kanban/kanban-context.ts +43 -0
- package/src/kanban/kanban-events.ts +34 -0
- package/src/kanban/kanban-generate-title.ts +259 -0
- package/src/kanban/kanban-generate-topic.ts +170 -0
- package/src/kanban/kanban-operations.ts +170 -0
- package/src/kanban/kanban-server.ts +931 -0
- package/src/kanban/kanban-turn-handlers.ts +55 -0
- package/src/kanban/model-capture.ts +34 -0
- package/src/log.ts +47 -0
- package/src/phases/env-sync.ts +64 -0
- package/src/phases/execution-mode.ts +79 -0
- package/src/phases/implement-to-verify.ts +31 -0
- package/src/phases/phase-progression.ts +249 -0
- package/src/phases/phase-transitions.ts +226 -0
- package/src/phases/workflow-router.ts +98 -0
- package/src/prompts/finish-instructions.ts +143 -0
- package/src/prompts/skill-block-builder.ts +73 -0
- package/src/prompts/skill-expansion.ts +262 -0
- package/src/prompts/task-gate-skill.ts +83 -0
- package/src/prompts/template-engine.ts +433 -0
- package/src/prompts/text-blocks.ts +279 -0
- package/src/review/review-context.ts +56 -0
- package/src/review/review-counter.ts +66 -0
- package/src/review/review-empty-loop-tracking.ts +52 -0
- package/src/review/review-loops.ts +217 -0
- package/src/review/review-report.ts +101 -0
- package/src/settings/model-overrides.ts +269 -0
- package/src/settings/settings-schema.ts +458 -0
- package/src/settings/settings-types.ts +81 -0
- package/src/settings/settings-ui.ts +222 -0
- package/src/shared/errors.ts +24 -0
- package/src/shared/global.d.ts +22 -0
- package/src/shared/session-entries.ts +24 -0
- package/src/shared/types.ts +195 -0
- package/src/shared/workflow-refs.ts +253 -0
- package/src/shared/workflow-types.ts +180 -0
- package/src/snippets/vendored/subscribe-to-dialog-coordinator.ts +98 -0
- package/src/snippets/vendored/subscribe-to-notifications.ts +150 -0
- package/src/snippets/vendored/subscribe-to-parallel-work-guardrail.ts +35 -0
- package/src/snippets/vendored/subscribe-to-subagent.ts +67 -0
- package/src/snippets/vendored/subscribe-to-todo.ts +63 -0
- package/src/state/archive-artifacts.ts +602 -0
- package/src/state/artifact-junction.ts +462 -0
- package/src/state/artifact-paths.ts +230 -0
- package/src/state/feature-activation.ts +239 -0
- package/src/state/feature-management.ts +325 -0
- package/src/state/feature-record-store.ts +39 -0
- package/src/state/feature-session.ts +263 -0
- package/src/state/feature-state.ts +439 -0
- package/src/state/post-turn-dispatch.ts +106 -0
- package/src/state/session-lifecycle.ts +346 -0
- package/src/state/state-persistence.ts +122 -0
- package/src/state/worth-notes.ts +89 -0
- package/src/tools/add-to-backlog.ts +117 -0
- package/src/tools/phase-ready.ts +742 -0
- package/src/tools/task-ready-advance.ts +235 -0
- package/src/tools/text-result.ts +15 -0
- package/src/ui/featyard-widget.ts +250 -0
- package/src/ui/manage-features-dialog.ts +246 -0
- package/src/ui/select-dialog.ts +27 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 avtc <tarasenkov@gmail.com>
|
|
3
|
+
|
|
4
|
+
// Template engine: applies {{PI_FY_*}} placeholder substitution to text.
|
|
5
|
+
// Pure mechanics (marker scan + handler dispatch); prompt content lives in text-blocks.ts,
|
|
6
|
+
// path builders live in state/artifact-paths.ts.
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
|
|
11
|
+
import { log } from "../log.js";
|
|
12
|
+
import type { Phase } from "../phases/phase-progression.js";
|
|
13
|
+
import { computeReviewMethod, isReviewPhase, toDisplayLoopNumber } from "../review/review-context.js";
|
|
14
|
+
import { getSettings } from "../settings/settings-ui.js";
|
|
15
|
+
import {
|
|
16
|
+
buildFallbackKnownIssuesPath,
|
|
17
|
+
buildFallbackReportPath,
|
|
18
|
+
buildReportFilePath,
|
|
19
|
+
FY_RESEARCH_DIR,
|
|
20
|
+
FY_TASK_PLANS_DIR,
|
|
21
|
+
resolveDesignRelativeDir,
|
|
22
|
+
resolveKnownIssuesPath,
|
|
23
|
+
slugifyTaskDesignation,
|
|
24
|
+
} from "../state/artifact-paths.js";
|
|
25
|
+
import { worthNotesPath } from "../state/worth-notes.js";
|
|
26
|
+
import {
|
|
27
|
+
ADDITIONAL_AREAS_OF_ATTENTION,
|
|
28
|
+
ARCHITECTURE_PRINCIPLES,
|
|
29
|
+
COMPREHENSIVE_DISPATCH_TEMPLATE,
|
|
30
|
+
COVERAGE_REVIEW_PROCESS,
|
|
31
|
+
DOC_COVERAGE_PROCESS,
|
|
32
|
+
GENERAL_DISPATCH_TEMPLATE,
|
|
33
|
+
IMPLEMENTER_GUIDANCE,
|
|
34
|
+
RESEARCHER_DELEGATION_SECTION,
|
|
35
|
+
VERIFY_PHASE_TEMPLATES,
|
|
36
|
+
} from "./text-blocks.js";
|
|
37
|
+
|
|
38
|
+
/** Default filesystem reference for report path builders. */
|
|
39
|
+
export const DEFAULT_FS = fs;
|
|
40
|
+
|
|
41
|
+
/** Options for placeholder substitution. */
|
|
42
|
+
export interface SubstitutePlaceholdersOptions {
|
|
43
|
+
emptyLoops?: Record<string, number>;
|
|
44
|
+
slug?: string;
|
|
45
|
+
loopIndex?: number;
|
|
46
|
+
/** Task number for fy-task-verifier report paths. */
|
|
47
|
+
taskName?: string;
|
|
48
|
+
/** Agent name for deriving report file paths. Includes -fork suffix for fork variants. */
|
|
49
|
+
agentName?: string | null;
|
|
50
|
+
/** LLM-generated topic (short subject name) for fallback report paths when no slug is active. */
|
|
51
|
+
topic?: string;
|
|
52
|
+
/** Base commit SHA captured at execution start. */
|
|
53
|
+
baseCommitSha?: string | null;
|
|
54
|
+
/** Current workflow phase. Drives review-iteration context placeholders. */
|
|
55
|
+
phase?: Phase;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Context object passed to each placeholder handler. */
|
|
59
|
+
interface PlaceholderContext {
|
|
60
|
+
text: string;
|
|
61
|
+
settings: ReturnType<typeof getSettings>;
|
|
62
|
+
opts: SubstitutePlaceholdersOptions;
|
|
63
|
+
/** Computed date string for report file paths. */
|
|
64
|
+
date: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A handler that replaces a placeholder in text. Returns the modified text. */
|
|
68
|
+
interface PlaceholderHandler {
|
|
69
|
+
/** Marker string or prefix to pre-scan for. Use full string for exact markers, or prefix ending with ':' for regex-based markers. */
|
|
70
|
+
readonly marker: string;
|
|
71
|
+
/** The replacement handler. */
|
|
72
|
+
handle: (ctx: PlaceholderContext) => string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Factory for report file path handlers — all follow the same slug-then-fallback pattern. */
|
|
76
|
+
function reportFileHandler(
|
|
77
|
+
marker: string,
|
|
78
|
+
role: string,
|
|
79
|
+
fallbackAgent: string,
|
|
80
|
+
taskPrefixOverride: ((ctx: PlaceholderContext) => string) | null,
|
|
81
|
+
): PlaceholderHandler {
|
|
82
|
+
return {
|
|
83
|
+
marker,
|
|
84
|
+
handle(ctx) {
|
|
85
|
+
const prefix = taskPrefixOverride ? taskPrefixOverride(ctx) : role;
|
|
86
|
+
const reportPath = ctx.opts.slug
|
|
87
|
+
? buildReportFilePath(ctx.opts.slug, prefix, ctx.opts.loopIndex ?? null, DEFAULT_FS)
|
|
88
|
+
: buildFallbackReportPath(ctx.date, ctx.opts.topic ?? prefix, ctx.opts.agentName ?? fallbackAgent, DEFAULT_FS);
|
|
89
|
+
return ctx.text.replaceAll(marker, reportPath);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Sentinel passed to reportFileHandler when no per-task prefix override is needed. */
|
|
95
|
+
const NO_TASK_PREFIX_OVERRIDE: ((ctx: PlaceholderContext) => string) | null = null;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Registry of placeholder handlers.
|
|
99
|
+
*
|
|
100
|
+
* No handler produces a {{PI_FY_*}} marker consumed by a later handler — each
|
|
101
|
+
* handler inlines any derived values directly into its output.
|
|
102
|
+
*/
|
|
103
|
+
const PLACEHOLDER_HANDLERS: PlaceholderHandler[] = [
|
|
104
|
+
// --- Static shared sections ---
|
|
105
|
+
|
|
106
|
+
// {{PI_FY_ARCHITECTURE_PRINCIPLES}} — canonical architecture principles (design/plan/fy-implement/fy-implementer)
|
|
107
|
+
{
|
|
108
|
+
marker: "{{PI_FY_ARCHITECTURE_PRINCIPLES}}",
|
|
109
|
+
handle(ctx) {
|
|
110
|
+
return ctx.text.replaceAll(this.marker, ARCHITECTURE_PRINCIPLES);
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// {{PI_FY_ADDITIONAL_AREAS_OF_ATTENTION}} — concerns-to-attend-to checklist (plan + fy-implementer)
|
|
115
|
+
{
|
|
116
|
+
marker: "{{PI_FY_ADDITIONAL_AREAS_OF_ATTENTION}}",
|
|
117
|
+
handle(ctx) {
|
|
118
|
+
return ctx.text.replaceAll(this.marker, ADDITIONAL_AREAS_OF_ATTENTION);
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// {{PI_FY_IMPLEMENTER_GUIDANCE}} — the fy-implementer's full guidance block (fy-implementer.md)
|
|
123
|
+
{
|
|
124
|
+
marker: "{{PI_FY_IMPLEMENTER_GUIDANCE}}",
|
|
125
|
+
handle(ctx) {
|
|
126
|
+
return ctx.text.replaceAll(this.marker, IMPLEMENTER_GUIDANCE);
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// --- Settings-derived instructions ---
|
|
131
|
+
|
|
132
|
+
// {{PI_FY_COVERAGE_REVIEW_PROCESS}} — coverage-first skeleton for the code reviewers
|
|
133
|
+
{
|
|
134
|
+
marker: "{{PI_FY_COVERAGE_REVIEW_PROCESS}}",
|
|
135
|
+
handle(ctx) {
|
|
136
|
+
return ctx.text.replaceAll(this.marker, COVERAGE_REVIEW_PROCESS);
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// {{PI_FY_DOC_COVERAGE_PROCESS}} — coverage-first skeleton for the doc reviewers
|
|
141
|
+
{
|
|
142
|
+
marker: "{{PI_FY_DOC_COVERAGE_PROCESS}}",
|
|
143
|
+
handle(ctx) {
|
|
144
|
+
return ctx.text.replaceAll(this.marker, DOC_COVERAGE_PROCESS);
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// {{PI_FY_REVIEWER_DISPATCH}} — includes skip instruction inline
|
|
149
|
+
{
|
|
150
|
+
marker: "{{PI_FY_REVIEWER_DISPATCH}}",
|
|
151
|
+
handle(ctx) {
|
|
152
|
+
const threshold = ctx.settings.reviewerSkipThreshold ?? 0;
|
|
153
|
+
const skipInstruction =
|
|
154
|
+
threshold === 0
|
|
155
|
+
? "Skipping is disabled — dispatch all relevant reviewers."
|
|
156
|
+
: `Reviewer skip: threshold=${threshold}.`;
|
|
157
|
+
const template =
|
|
158
|
+
ctx.settings.featureReviewMode === "comprehensive"
|
|
159
|
+
? COMPREHENSIVE_DISPATCH_TEMPLATE
|
|
160
|
+
: GENERAL_DISPATCH_TEMPLATE;
|
|
161
|
+
return ctx.text.replaceAll(this.marker, `${skipInstruction}\n\n${template}`);
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// {{PI_FY_REVIEWER_SKIP}}
|
|
166
|
+
{
|
|
167
|
+
marker: "{{PI_FY_REVIEWER_SKIP}}",
|
|
168
|
+
handle(ctx) {
|
|
169
|
+
const threshold = ctx.settings.reviewerSkipThreshold;
|
|
170
|
+
const emptyLoopsStr = ctx.opts.emptyLoops ? JSON.stringify(ctx.opts.emptyLoops) : "{}";
|
|
171
|
+
const instruction = [
|
|
172
|
+
`Reviewer skip: threshold=${threshold}.`,
|
|
173
|
+
`Empty loop counts: ${emptyLoopsStr}.`,
|
|
174
|
+
threshold === 0
|
|
175
|
+
? "Skipping is disabled — dispatch all relevant reviewers."
|
|
176
|
+
: `Skip reviewers with empty count >= ${threshold}.`,
|
|
177
|
+
].join(" ");
|
|
178
|
+
return ctx.text.replaceAll(this.marker, instruction);
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// {{PI_FY_VERIFY_PHASES:<phase>}}
|
|
183
|
+
// Emits the full verifier spawn block with the iteration count inlined directly
|
|
184
|
+
// (no intermediate {{PI_FY_VERIFY_ITERATIONS}} marker is produced).
|
|
185
|
+
{
|
|
186
|
+
marker: "{{PI_FY_VERIFY_PHASES:",
|
|
187
|
+
handle(ctx) {
|
|
188
|
+
const iterations = ctx.settings.maxVerifyRounds ?? 3;
|
|
189
|
+
return ctx.text.replace(/\{\{PI_FY_VERIFY_PHASES:(verify|plan)\}\}/g, (_match, phase: "verify" | "plan") => {
|
|
190
|
+
const phases = ctx.settings.verifyPhases ?? "verify";
|
|
191
|
+
if (phases === "off") return "";
|
|
192
|
+
const phaseList = phases.includes("+") ? phases.split("+") : [phases];
|
|
193
|
+
if (!phaseList.includes(phase)) return "";
|
|
194
|
+
return (VERIFY_PHASE_TEMPLATES[phase] ?? "").replaceAll("{{PI_FY_VERIFY_ITERATIONS}}", String(iterations));
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
// --- Report file paths (share lazily-computed date) ---
|
|
200
|
+
|
|
201
|
+
reportFileHandler("{{PI_FY_DESIGN_REPORT_FILE}}", "design-review", "design-reviewer", NO_TASK_PREFIX_OVERRIDE),
|
|
202
|
+
reportFileHandler("{{PI_FY_PLAN_REPORT_FILE}}", "plan-review", "plan-reviewer", NO_TASK_PREFIX_OVERRIDE),
|
|
203
|
+
reportFileHandler("{{PI_FY_REVIEW_REPORT_FILE}}", "review", "reviewer", NO_TASK_PREFIX_OVERRIDE),
|
|
204
|
+
|
|
205
|
+
// {{PI_FY_REPORT_FILE}} — resolved from: (1) derived from slug+agentName+taskName, (2) date-based fallback
|
|
206
|
+
{
|
|
207
|
+
marker: "{{PI_FY_REPORT_FILE}}",
|
|
208
|
+
handle(ctx) {
|
|
209
|
+
let reportPath: string;
|
|
210
|
+
if (ctx.opts.slug && ctx.opts.agentName) {
|
|
211
|
+
// Include task designation in prefix for per-task review differentiation
|
|
212
|
+
// (e.g. task-3-wire-the-login-form-fy-general-reviewer); slugify for path safety
|
|
213
|
+
const prefix = ctx.opts.taskName
|
|
214
|
+
? `task-${slugifyTaskDesignation(ctx.opts.taskName)}-${ctx.opts.agentName}`
|
|
215
|
+
: ctx.opts.agentName;
|
|
216
|
+
reportPath = buildReportFilePath(ctx.opts.slug, prefix, ctx.opts.loopIndex ?? null, DEFAULT_FS);
|
|
217
|
+
} else {
|
|
218
|
+
reportPath = buildFallbackReportPath(
|
|
219
|
+
ctx.date,
|
|
220
|
+
ctx.opts.topic ?? "review",
|
|
221
|
+
ctx.opts.agentName ?? undefined,
|
|
222
|
+
DEFAULT_FS,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return ctx.text.replaceAll(this.marker, reportPath);
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// --- Document paths ---
|
|
230
|
+
|
|
231
|
+
// {{PI_FY_DESIGN_DOC_PATH}}
|
|
232
|
+
{
|
|
233
|
+
marker: "{{PI_FY_DESIGN_DOC_PATH}}",
|
|
234
|
+
handle(ctx) {
|
|
235
|
+
const designDir = resolveDesignRelativeDir(getSettings().designDocStorage);
|
|
236
|
+
const docPath = ctx.opts.slug
|
|
237
|
+
? `${designDir}/${ctx.opts.slug}-design.md`
|
|
238
|
+
: `${designDir}/YYYY-MM-DD-<topic>-design.md`;
|
|
239
|
+
return ctx.text.replaceAll(this.marker, docPath);
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
// {{PI_FY_DESIGN_RELATIVE_DIR}} — the relative design-doc directory, resolved from the
|
|
244
|
+
// designDocStorage setting (committed → docs/featyard/designs, local → .featyard/designs).
|
|
245
|
+
{
|
|
246
|
+
marker: "{{PI_FY_DESIGN_RELATIVE_DIR}}",
|
|
247
|
+
handle(ctx) {
|
|
248
|
+
const designDir = resolveDesignRelativeDir(getSettings().designDocStorage);
|
|
249
|
+
return ctx.text.replaceAll(this.marker, designDir);
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
// {{PI_FY_DESIGN_HANDOFF}} — the design-phase hand-off step, parameterized by designDocStorage:
|
|
254
|
+
// committed docs are committed to git; local docs are gitignored and must NOT be committed.
|
|
255
|
+
{
|
|
256
|
+
marker: "{{PI_FY_DESIGN_HANDOFF}}",
|
|
257
|
+
handle(ctx) {
|
|
258
|
+
const step =
|
|
259
|
+
getSettings().designDocStorage === "committed"
|
|
260
|
+
? "Commit the design file, then signal the phase complete with the `phase_ready` tool. End your turn."
|
|
261
|
+
: "The design file is gitignored under `.featyard/` (do not commit). Signal the phase complete with the `phase_ready` tool. End your turn.";
|
|
262
|
+
return ctx.text.replaceAll(this.marker, step);
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
// {{PI_FY_PLAN_DOC_PATH}}
|
|
267
|
+
{
|
|
268
|
+
marker: "{{PI_FY_PLAN_DOC_PATH}}",
|
|
269
|
+
handle(ctx) {
|
|
270
|
+
// Slug present: stable per-feature path. No slug: date-wrapped fallback so age-clean
|
|
271
|
+
// can target a bare <date> dir under task-plans/ (mirrors the reviews/ pattern). The
|
|
272
|
+
// <date>/<date>- wrapper uses the real ctx.date, never the literal 'YYYY-MM-DD'.
|
|
273
|
+
const docPath = ctx.opts.slug
|
|
274
|
+
? `${FY_TASK_PLANS_DIR}/${ctx.opts.slug}-task-plan.md`
|
|
275
|
+
: `${FY_TASK_PLANS_DIR}/${ctx.date}/${ctx.date}-<feature-name>-task-plan.md`;
|
|
276
|
+
return ctx.text.replaceAll(this.marker, docPath);
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
// {{PI_FY_FEATURE_SLUG}}
|
|
281
|
+
{
|
|
282
|
+
marker: "{{PI_FY_FEATURE_SLUG}}",
|
|
283
|
+
handle(ctx) {
|
|
284
|
+
return ctx.text.replaceAll(this.marker, ctx.opts.slug ?? "YYYY-MM-DD-<topic>");
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
// {{PI_FY_RESEARCH_DIR}} — absolute path to research output directory
|
|
289
|
+
{
|
|
290
|
+
marker: "{{PI_FY_RESEARCH_DIR}}",
|
|
291
|
+
handle(ctx) {
|
|
292
|
+
// Slug present: stable per-feature dir. No slug: date-wrapped fallback so age-clean
|
|
293
|
+
// can target a bare <date> dir under research/ (mirrors the reviews/ pattern). The
|
|
294
|
+
// <date>/<date>- wrapper uses the real ctx.date, never the literal 'YYYY-MM-DD'.
|
|
295
|
+
const researchDir = ctx.opts.slug
|
|
296
|
+
? path.resolve(process.cwd(), FY_RESEARCH_DIR, ctx.opts.slug)
|
|
297
|
+
: path.resolve(process.cwd(), FY_RESEARCH_DIR, ctx.date, `${ctx.date}-<topic>`);
|
|
298
|
+
return ctx.text.replaceAll(this.marker, researchDir);
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
// {{PI_FY_CURRENT_TASK}} — the plan-task designation set by task_ready_advance (number + name).
|
|
303
|
+
{
|
|
304
|
+
marker: "{{PI_FY_CURRENT_TASK}}",
|
|
305
|
+
handle(ctx) {
|
|
306
|
+
return ctx.text.replaceAll(this.marker, ctx.opts.taskName ?? "(not available)");
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
// {{PI_FY_BASE_COMMIT_SHA}}
|
|
311
|
+
{
|
|
312
|
+
marker: "{{PI_FY_BASE_COMMIT_SHA}}",
|
|
313
|
+
handle(ctx) {
|
|
314
|
+
return ctx.text.replaceAll(this.marker, ctx.opts.baseCommitSha ?? "(not available)");
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
// {{PI_FY_KNOWN_ISSUES_PATH}} — phase-scoped so design and plan dismissals stay separate
|
|
319
|
+
// from each other and from code-review dismissals.
|
|
320
|
+
{
|
|
321
|
+
marker: "{{PI_FY_KNOWN_ISSUES_PATH}}",
|
|
322
|
+
handle(ctx) {
|
|
323
|
+
// Slug present: phase-scoped path under the feature's review dir.
|
|
324
|
+
// No slug: date-based fallback so a manual skill invocation without an active
|
|
325
|
+
// workflow still has a writable known-issues file (mirrors report-path fallback).
|
|
326
|
+
// resolveKnownIssuesPath returns null only when slug is absent, so the slug-present
|
|
327
|
+
// branch is always non-null.
|
|
328
|
+
const issuesPath = ctx.opts.slug
|
|
329
|
+
? (resolveKnownIssuesPath(ctx.opts.slug, ctx.opts.phase, ctx.opts.taskName) as string)
|
|
330
|
+
: buildFallbackKnownIssuesPath(ctx.date, ctx.opts.phase, ctx.opts.taskName);
|
|
331
|
+
return ctx.text.replaceAll(this.marker, issuesPath);
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
// {{PI_FY_WORTH_NOTES_PATH}} — worth-notes doc for out-of-scope smells/bugs/oddities the
|
|
336
|
+
// fy-implementer reports but does not fix (parallel to known-issues; written by the orchestrator).
|
|
337
|
+
{
|
|
338
|
+
marker: "{{PI_FY_WORTH_NOTES_PATH}}",
|
|
339
|
+
handle(ctx) {
|
|
340
|
+
return ctx.text.replaceAll(this.marker, worthNotesPath(ctx.opts.slug ?? null, ctx.date));
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
// --- Researcher settings ---
|
|
345
|
+
|
|
346
|
+
// {{PI_FY_RESEARCHER_MIN}}
|
|
347
|
+
{
|
|
348
|
+
marker: "{{PI_FY_RESEARCHER_MIN}}",
|
|
349
|
+
handle(ctx) {
|
|
350
|
+
return ctx.text.replaceAll(this.marker, String(ctx.settings.researcherMinInstances));
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
// {{PI_FY_RESEARCHER_MAX}}
|
|
355
|
+
{
|
|
356
|
+
marker: "{{PI_FY_RESEARCHER_MAX}}",
|
|
357
|
+
handle(ctx) {
|
|
358
|
+
return ctx.text.replaceAll(this.marker, String(ctx.settings.researcherMaxInstances));
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
// {{PI_FY_RESEARCHER_DELEGATION}} — inject delegation section when nestedResearchers is "on"
|
|
363
|
+
{
|
|
364
|
+
marker: "{{PI_FY_RESEARCHER_DELEGATION}}",
|
|
365
|
+
handle(ctx) {
|
|
366
|
+
return ctx.text.replaceAll(
|
|
367
|
+
this.marker,
|
|
368
|
+
ctx.settings.nestedResearchers === "on" ? RESEARCHER_DELEGATION_SECTION : "",
|
|
369
|
+
);
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
// --- Review-iteration context (fy-design-review / fy-plan-review skills) ---
|
|
374
|
+
|
|
375
|
+
// {{PI_FY_REVIEW_METHOD}}
|
|
376
|
+
{
|
|
377
|
+
marker: "{{PI_FY_REVIEW_METHOD}}",
|
|
378
|
+
handle(ctx) {
|
|
379
|
+
// Outside a review phase the method is indeterminate; resolve to empty so
|
|
380
|
+
// the skill reads cleanly rather than showing a raw placeholder.
|
|
381
|
+
if (!isReviewPhase(ctx.opts.phase)) {
|
|
382
|
+
return ctx.text.replaceAll(this.marker, "");
|
|
383
|
+
}
|
|
384
|
+
const isDesign = ctx.opts.phase === "design";
|
|
385
|
+
const method = computeReviewMethod(isDesign, ctx.settings);
|
|
386
|
+
return ctx.text.replaceAll(this.marker, method);
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
// {{PI_FY_REVIEW_LOOP_NUMBER}} — the iteration being reviewed (display number).
|
|
391
|
+
{
|
|
392
|
+
marker: "{{PI_FY_REVIEW_LOOP_NUMBER}}",
|
|
393
|
+
handle(ctx) {
|
|
394
|
+
if (!isReviewPhase(ctx.opts.phase)) {
|
|
395
|
+
return ctx.text.replaceAll(this.marker, "");
|
|
396
|
+
}
|
|
397
|
+
const loopNumber = toDisplayLoopNumber(ctx.opts.loopIndex ?? 1);
|
|
398
|
+
return ctx.text.replaceAll(this.marker, String(loopNumber));
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Apply generic {{PI_FY_*}} placeholder substitution to text.
|
|
405
|
+
* Replaces settings-derived placeholders with their resolved values.
|
|
406
|
+
* Returns the original text if no placeholders are found.
|
|
407
|
+
*/
|
|
408
|
+
export function substitutePlaceholders(text: string, opts: SubstitutePlaceholdersOptions): string {
|
|
409
|
+
if (!text.includes("{{PI_FY_")) return text;
|
|
410
|
+
|
|
411
|
+
const settings = getSettings();
|
|
412
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
413
|
+
|
|
414
|
+
const ctx: PlaceholderContext = {
|
|
415
|
+
text,
|
|
416
|
+
settings,
|
|
417
|
+
opts,
|
|
418
|
+
date,
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
for (const handler of PLACEHOLDER_HANDLERS) {
|
|
422
|
+
if (!ctx.text.includes(handler.marker)) continue;
|
|
423
|
+
ctx.text = handler.handle(ctx);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Post-pass validation: warn about any unresolved PI_FY_ placeholders
|
|
427
|
+
const unresolved = ctx.text.match(/\{\{PI_FY_[A-Z_]+\}\}/g);
|
|
428
|
+
if (unresolved) {
|
|
429
|
+
log.warn(`[template-engine] Unresolved PI_FY_ placeholders after substitution pass: ${unresolved.join(", ")}`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return ctx.text;
|
|
433
|
+
}
|