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,262 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 avtc <tarasenkov@gmail.com>
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Skill expansion — template substitution and skill command expansion.
|
|
6
|
+
* Handles all {{PI_FY_*}} placeholder resolution and finish instruction generation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getBranchOrShortSha } from "../git/git-queries.js";
|
|
10
|
+
import { resolveMainRepoPathSync } from "../git/worktrees/worktree-lifecycle.js";
|
|
11
|
+
import type { AutoAgentCallback } from "../kanban/auto-agent/auto-agent-state-machine.js";
|
|
12
|
+
import { getLoopCountForPhase } from "../phases/phase-transitions.js";
|
|
13
|
+
import {
|
|
14
|
+
buildCurrentBranchAutoAgentSection,
|
|
15
|
+
buildCurrentBranchInteractiveSection,
|
|
16
|
+
buildWorktreeAutoAgentSection,
|
|
17
|
+
buildWorktreeInteractiveSection,
|
|
18
|
+
type FinishContext,
|
|
19
|
+
} from "../prompts/finish-instructions.js";
|
|
20
|
+
import { expandSkillCommand as _expandSkillCommand } from "../prompts/skill-block-builder.js";
|
|
21
|
+
import { substitutePlaceholders } from "../prompts/template-engine.js";
|
|
22
|
+
import { IMPLEMENTER_GUIDANCE } from "../prompts/text-blocks.js";
|
|
23
|
+
import { getSettings } from "../settings/settings-ui.js";
|
|
24
|
+
import type { FeatureSession } from "../state/feature-session.js";
|
|
25
|
+
import { type FeatureState, flowToExecutionMode, isSubagentMode } from "../state/feature-state.js";
|
|
26
|
+
|
|
27
|
+
/** Pass as `handler` when no handler reference is available. */
|
|
28
|
+
export const NO_HANDLER: FeatureSession | null = null;
|
|
29
|
+
/** Function type of the `getEmptyLoopsForSlug` skill-expansion param (empty-loop counts per slug, or null). */
|
|
30
|
+
export type GetEmptyLoopsForSlug = ((slug: string) => Record<string, number> | undefined) | null;
|
|
31
|
+
/** Function type of the `getAutoAgentCb` skill-expansion param (auto-agent callback provider, or null). */
|
|
32
|
+
export type GetAutoAgentCb = (() => AutoAgentCallback | null) | null;
|
|
33
|
+
/** Pass as `getEmptyLoopsForSlug` when no empty-loop tracking is available. */
|
|
34
|
+
export const NO_EMPTY_LOOPS_FN: GetEmptyLoopsForSlug = null;
|
|
35
|
+
/** Pass as `featureStateOverride` when no override is needed. */
|
|
36
|
+
export const NO_FEATURE_STATE_OVERRIDE: FeatureState | null = null;
|
|
37
|
+
/** Pass as `getAutoAgentCb` when no auto-agent callback is available. */
|
|
38
|
+
export const NO_AUTO_AGENT_CB: GetAutoAgentCb = null;
|
|
39
|
+
|
|
40
|
+
export interface SubstitutionResult {
|
|
41
|
+
text: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildWorktreeContextSection(
|
|
45
|
+
worktreePath: string,
|
|
46
|
+
slug: string,
|
|
47
|
+
baseBranch: string,
|
|
48
|
+
mainRepoPath: string | null,
|
|
49
|
+
): string {
|
|
50
|
+
return [
|
|
51
|
+
"## Worktree Context",
|
|
52
|
+
"",
|
|
53
|
+
"You are working in a git worktree created by the extension.",
|
|
54
|
+
`- Worktree path: ${worktreePath}`,
|
|
55
|
+
...(mainRepoPath ? [`- Main repo path: ${mainRepoPath}`] : []),
|
|
56
|
+
`- Feature branch: feature/${slug}`,
|
|
57
|
+
`- Base branch: ${baseBranch}`,
|
|
58
|
+
"",
|
|
59
|
+
"You are working in a git worktree. The extension rewrites all tool paths automatically — use relative paths as normal.",
|
|
60
|
+
"",
|
|
61
|
+
`All subagent dispatches MUST include \`cwd: "${worktreePath}"\` — this applies to all modes: single (\`cwd\`), parallel (\`tasks[].cwd\`), and chain (\`chain[].cwd\`). Subagents without \`cwd\` start in the main repo, not the worktree.`,
|
|
62
|
+
"",
|
|
63
|
+
"To sync from base branch:",
|
|
64
|
+
"1. `git remote` — check if remote exists",
|
|
65
|
+
`2. If remote exists: \`git fetch origin && git merge origin/${baseBranch}\``,
|
|
66
|
+
`3. If no remote: \`git merge ${baseBranch}\` (local branch only)`,
|
|
67
|
+
"",
|
|
68
|
+
"Do NOT create or remove worktrees — the extension manages them.",
|
|
69
|
+
].join("\n");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Substitute all known PI_FY_* template placeholders in the given text.
|
|
74
|
+
* Returns the modified text (or original if no placeholders found) and
|
|
75
|
+
* metadata about whether the guardrail whitelist should be activated.
|
|
76
|
+
*/
|
|
77
|
+
export function substituteTemplates(
|
|
78
|
+
text: string,
|
|
79
|
+
handler: FeatureSession | null,
|
|
80
|
+
getEmptyLoopsForSlug: GetEmptyLoopsForSlug,
|
|
81
|
+
featureStateOverride: FeatureState | null,
|
|
82
|
+
getAutoAgentCb: GetAutoAgentCb,
|
|
83
|
+
agentName: string | null,
|
|
84
|
+
): SubstitutionResult {
|
|
85
|
+
const settings = getSettings();
|
|
86
|
+
|
|
87
|
+
// Cache feature state for the active slug — avoids repeated disk reads + JSON.parse.
|
|
88
|
+
// Derive the slug from the handler when available, else from the override (so generic
|
|
89
|
+
// placeholders like {{PI_FY_WORTH_NOTES_PATH}} resolve in tests/dispatch without a handler).
|
|
90
|
+
const cachedFeatureState = featureStateOverride ?? handler?.getActiveFeatureState() ?? undefined;
|
|
91
|
+
const activeSlug = handler?.getActiveFeatureSlug() ?? cachedFeatureState?.featureSlug ?? undefined;
|
|
92
|
+
|
|
93
|
+
// {{PI_FY_IMPLEMENT_MODE}} — inject fy-implementer guidance (current-session) or orchestrator
|
|
94
|
+
// dispatch instructions (subagent) based on executionMode.
|
|
95
|
+
// - current-session: the main agent implements directly, so it gets the FULL fy-implementer guidance
|
|
96
|
+
// (IMPLEMENTER_GUIDANCE — pre-resolved with architecture principles + attention inlined, so no
|
|
97
|
+
// nested substitution; it carries Blockers, so fy-implement needs no separate When-to-Stop).
|
|
98
|
+
// - subagent: the main agent is the orchestrator (dispatches fy-implementer subagents that carry their
|
|
99
|
+
// own guidance); it gets dispatch instructions + escalation (when to stop/escalate to the user).
|
|
100
|
+
if (text.includes("{{PI_FY_IMPLEMENT_MODE}}")) {
|
|
101
|
+
const subagent = isSubagentMode(flowToExecutionMode(getSettings().implementMode));
|
|
102
|
+
const instruction = subagent
|
|
103
|
+
? [
|
|
104
|
+
"Dispatch an fy-implementer subagent:",
|
|
105
|
+
"",
|
|
106
|
+
"```ts",
|
|
107
|
+
'subagent({ agent: "fy-implementer", task: "... task prompt ..." })',
|
|
108
|
+
"```",
|
|
109
|
+
"",
|
|
110
|
+
"Task prompt should include: task number, design section, scene-setting context (dependencies from previous tasks), working directory.",
|
|
111
|
+
"",
|
|
112
|
+
"If the fy-implementer reports uncertainties or assumptions: re-dispatch with clarifications if critical, or accept and note for review.",
|
|
113
|
+
"",
|
|
114
|
+
"⚠️ Never dispatch multiple implementation subagents in parallel (file conflicts).",
|
|
115
|
+
"⚠️ Never write code yourself — you are the orchestrator.",
|
|
116
|
+
"",
|
|
117
|
+
"**Escalate to the user and stop if an fy-implementer subagent:** reports a blocker it cannot resolve, reports that the plan is wrong, or fails twice on the same task. Do not silently work around it.",
|
|
118
|
+
].join("\n")
|
|
119
|
+
: IMPLEMENTER_GUIDANCE;
|
|
120
|
+
text = text.replaceAll("{{PI_FY_IMPLEMENT_MODE}}", instruction);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// {{PI_FY_WORTH_NOTES}} — worth-notes handling, branches on executionMode.
|
|
124
|
+
// Subagent mode: the implementer reports worth-notes; the orchestrator appends them.
|
|
125
|
+
// Direct mode: the in-session implementer appends its own worth-notes directly.
|
|
126
|
+
if (text.includes("{{PI_FY_WORTH_NOTES}}")) {
|
|
127
|
+
const subagent = isSubagentMode(flowToExecutionMode(getSettings().implementMode));
|
|
128
|
+
const instruction = subagent
|
|
129
|
+
? "Collect worth-notes from each implementer subagent's final report and append them to `{{PI_FY_WORTH_NOTES_PATH}}`: out-of-scope code smells needing refactoring, bugs you could not fix, and anything strange (what and where)."
|
|
130
|
+
: "When you notice an out-of-scope code smell, a bug you cannot fix, or anything strange during your work, append it to `{{PI_FY_WORTH_NOTES_PATH}}` (what and where).";
|
|
131
|
+
text = text.replaceAll("{{PI_FY_WORTH_NOTES}}", instruction);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// {{PI_FY_WORKTREE_CONTEXT}} — worktree paths, subagent CWD, sync commands
|
|
135
|
+
if (text.includes("{{PI_FY_WORKTREE_CONTEXT}}")) {
|
|
136
|
+
if (settings.branchPolicy === "worktree") {
|
|
137
|
+
const slug = handler?.getActiveFeatureSlug() ?? null;
|
|
138
|
+
if (!slug) {
|
|
139
|
+
// No active feature — expand to empty (not a worktree failure)
|
|
140
|
+
text = text.replaceAll("{{PI_FY_WORKTREE_CONTEXT}}", "");
|
|
141
|
+
} else {
|
|
142
|
+
const fsData = cachedFeatureState;
|
|
143
|
+
if (fsData?.git?.worktreePath) {
|
|
144
|
+
const mainRepoPath = resolveMainRepoPathSync();
|
|
145
|
+
const baseBranch = fsData.git.baseBranch ?? settings.baseBranch ?? "main";
|
|
146
|
+
text = text.replaceAll(
|
|
147
|
+
"{{PI_FY_WORKTREE_CONTEXT}}",
|
|
148
|
+
buildWorktreeContextSection(fsData.git.worktreePath, slug, baseBranch, mainRepoPath),
|
|
149
|
+
);
|
|
150
|
+
} else {
|
|
151
|
+
// Worktree creation failed
|
|
152
|
+
text = text.replaceAll(
|
|
153
|
+
"{{PI_FY_WORKTREE_CONTEXT}}",
|
|
154
|
+
"⚠️ Worktree creation failed. You are working in the main repo directory. Do NOT set cwd on subagent dispatches — let them inherit the main repo path.",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
// current-branch policy — no worktree context
|
|
160
|
+
text = text.replaceAll("{{PI_FY_WORKTREE_CONTEXT}}", "");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// {{PI_FY_FINISH_INSTRUCTIONS}}
|
|
165
|
+
// 4 variants: branchPolicy × autoAgentActive
|
|
166
|
+
const hasFinishPlaceholder = text.includes("{{PI_FY_FINISH_INSTRUCTIONS}}");
|
|
167
|
+
if (hasFinishPlaceholder) {
|
|
168
|
+
const autoAgentCb = getAutoAgentCb?.();
|
|
169
|
+
const isActive = autoAgentCb?.isActive?.();
|
|
170
|
+
const branchPolicy = settings.branchPolicy ?? "current-branch";
|
|
171
|
+
const settingsBaseBranch = settings.baseBranch ?? null;
|
|
172
|
+
// Feature state baseBranch takes precedence (set during worktree creation — see DD-11)
|
|
173
|
+
let baseBranch = settingsBaseBranch;
|
|
174
|
+
let featureWorktreePath: string | null = null;
|
|
175
|
+
if (activeSlug) {
|
|
176
|
+
const featureState = cachedFeatureState;
|
|
177
|
+
if (featureState?.git?.baseBranch) {
|
|
178
|
+
baseBranch = featureState.git.baseBranch;
|
|
179
|
+
}
|
|
180
|
+
if (featureState?.git?.worktreePath) {
|
|
181
|
+
featureWorktreePath = featureState.git.worktreePath;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const currentBranch = getBranchOrShortSha(process.cwd());
|
|
185
|
+
|
|
186
|
+
const finishCtx: FinishContext = {
|
|
187
|
+
baseBranch,
|
|
188
|
+
currentBranch,
|
|
189
|
+
worktreePath: featureWorktreePath,
|
|
190
|
+
mainRepoPath: null,
|
|
191
|
+
slug: activeSlug,
|
|
192
|
+
worktreeFallbackWarning:
|
|
193
|
+
branchPolicy === "worktree" && !featureWorktreePath
|
|
194
|
+
? "⚠️ Worktree creation failed — code is on feature branch, NOT merged to baseBranch. Merge manually."
|
|
195
|
+
: null,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
let section: string;
|
|
199
|
+
|
|
200
|
+
if (branchPolicy === "worktree" && featureWorktreePath) {
|
|
201
|
+
// --- Worktree policy ---
|
|
202
|
+
finishCtx.mainRepoPath = resolveMainRepoPathSync();
|
|
203
|
+
|
|
204
|
+
section = isActive ? buildWorktreeAutoAgentSection(finishCtx) : buildWorktreeInteractiveSection(finishCtx);
|
|
205
|
+
} else {
|
|
206
|
+
// --- Current-branch policy (or worktree without worktreePath — fallback) ---
|
|
207
|
+
section = isActive
|
|
208
|
+
? buildCurrentBranchAutoAgentSection(finishCtx)
|
|
209
|
+
: buildCurrentBranchInteractiveSection(finishCtx);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
text = text.replaceAll("{{PI_FY_FINISH_INSTRUCTIONS}}", section);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Apply generic placeholder substitution (PI_FY_REVIEWER_SKIP, etc.)
|
|
216
|
+
const emptyLoops = activeSlug ? getEmptyLoopsForSlug?.(activeSlug) : undefined;
|
|
217
|
+
|
|
218
|
+
// Current workflow phase — drives review-iteration context placeholders
|
|
219
|
+
// (REVIEW_LOOP_CONTEXT, REVIEW_METHOD) and loop-index derivation.
|
|
220
|
+
const phase = handler?.getWorkflowState()?.currentPhase;
|
|
221
|
+
|
|
222
|
+
// Derive loop index from feature state based on current phase for report file naming
|
|
223
|
+
let loopIndex: number | undefined;
|
|
224
|
+
if (activeSlug && phase) {
|
|
225
|
+
loopIndex = getLoopCountForPhase(cachedFeatureState ?? null, phase);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
text: substitutePlaceholders(text, {
|
|
230
|
+
emptyLoops,
|
|
231
|
+
slug: activeSlug,
|
|
232
|
+
loopIndex,
|
|
233
|
+
baseCommitSha: cachedFeatureState?.git?.baseCommitSha ?? undefined,
|
|
234
|
+
agentName,
|
|
235
|
+
phase: phase ?? undefined,
|
|
236
|
+
taskName: cachedFeatureState?.implement.currentTask ?? undefined,
|
|
237
|
+
}),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Expand a /skill:name command into the <skill> XML block that pi core
|
|
243
|
+
* would produce via _expandSkillCommand(). Also substitutes {{PI_FY_*}}
|
|
244
|
+
* template placeholders in the skill body.
|
|
245
|
+
*
|
|
246
|
+
* Returns the expanded text, or the original text if the skill is not found
|
|
247
|
+
* or on error (matching pi core's _expandSkillCommand behavior).
|
|
248
|
+
*/
|
|
249
|
+
export function expandSkillCommand(
|
|
250
|
+
text: string,
|
|
251
|
+
handler: FeatureSession | null,
|
|
252
|
+
getEmptyLoopsForSlug: GetEmptyLoopsForSlug,
|
|
253
|
+
featureStateOverride: FeatureState | null,
|
|
254
|
+
getAutoAgentCb: GetAutoAgentCb,
|
|
255
|
+
agentName: string | null,
|
|
256
|
+
): string {
|
|
257
|
+
const result = _expandSkillCommand(
|
|
258
|
+
text,
|
|
259
|
+
(t) => substituteTemplates(t, handler, getEmptyLoopsForSlug, featureStateOverride, getAutoAgentCb, agentName).text,
|
|
260
|
+
);
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 avtc <tarasenkov@gmail.com>
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The dispatched per-task gate mini-skill (`fy-task-gate`).
|
|
6
|
+
*
|
|
7
|
+
* In the dispatch model, `task_ready_advance` decides after each call whether to
|
|
8
|
+
* dispatch another gate round or advance. When it dispatches, it sends a fresh
|
|
9
|
+
* `<skill name="fy-task-gate">` block via `sendUserMessage({ deliverAs: "steer" })`
|
|
10
|
+
* — authoritative (survives compaction), arrives after turn-end or the next tool
|
|
11
|
+
* call (robust to the model not ending its turn).
|
|
12
|
+
*
|
|
13
|
+
* This is a CODE CONSTANT, NOT a registered `skills/` file: it is never
|
|
14
|
+
* `/skill`-invoked by the user. The non-empty `location=" "` makes the TUI
|
|
15
|
+
* collapse the block into one `fy-task-gate` item.
|
|
16
|
+
*
|
|
17
|
+
* `buildTaskGateSkill` assembles only the gates the caller selected to run
|
|
18
|
+
* (the caller decides which gates to run per the asymmetric respawn rule). It leaves `{{PI_FY_*}}` markers INTACT — the context
|
|
19
|
+
* handler substitutes them inside `<skill>` blocks at the next LLM call, with the
|
|
20
|
+
* real feature slug/phase; pre-substituting here (no slug/phase in scope) would
|
|
21
|
+
* lock in a wrong date-based path the context handler cannot fix.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export interface TaskGateSkillInput {
|
|
25
|
+
/** 1-indexed round number being dispatched (round 1 on entry, then round+1 on reloop). */
|
|
26
|
+
round: number;
|
|
27
|
+
/** The current task the gate is gating (the model is told it is not ready to advance). */
|
|
28
|
+
task: string;
|
|
29
|
+
/** The next task to advance to, or undefined when on the last task (nextTask omitted in the example call). */
|
|
30
|
+
next: string | undefined;
|
|
31
|
+
/** Dispatch the #### Verify section (fy-task-verifier). */
|
|
32
|
+
runVerifier: boolean;
|
|
33
|
+
/** Dispatch the #### Review section (fy-general-reviewer). */
|
|
34
|
+
runReviewer: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Escape model-provided task names so they cannot break the `<skill>` tag boundary, the
|
|
38
|
+
* `nextTask: "..."` quoting, or the markdown code spans in the skill body. The task/next
|
|
39
|
+
* values originate from plan content; this is defense-in-depth, not an adversarial sink.
|
|
40
|
+
* Exported because the same task name is interpolated into tool-result / compact
|
|
41
|
+
* follow-up messages that also reach pi's message pipeline. */
|
|
42
|
+
export function sanitizeSkillText(value: string): string {
|
|
43
|
+
return value.replace(/</g, "<").replace(/"/g, """).replace(/`/g, "'");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Assemble the per-task gate skill body and wrap it as a collapsed `<skill>` block. */
|
|
47
|
+
export function buildTaskGateSkill({ round, task, next, runVerifier, runReviewer }: TaskGateSkillInput): string {
|
|
48
|
+
const safeTask = sanitizeSkillText(task);
|
|
49
|
+
const safeNext = next === undefined ? undefined : sanitizeSkillText(next);
|
|
50
|
+
const nextParam = safeNext === undefined ? "" : `, nextTask: "${safeNext}"`;
|
|
51
|
+
|
|
52
|
+
const verifySection = runVerifier
|
|
53
|
+
? `
|
|
54
|
+
#### Verify
|
|
55
|
+
Spawn fy-task-verifier — \`subagent({ agent: "fy-task-verifier", task: "..." })\`.
|
|
56
|
+
Task prompt: diff base (the commit before this task's first commit — fixed across all verify rounds; if unknown, find it with \`git log\`), diff head (current HEAD).
|
|
57
|
+
Treat \`⏭️ deferred\` like \`❌ missing\`: the work is not done.
|
|
58
|
+
`
|
|
59
|
+
: "";
|
|
60
|
+
|
|
61
|
+
const reviewSection = runReviewer
|
|
62
|
+
? `
|
|
63
|
+
#### Review
|
|
64
|
+
Spawn fy-general-reviewer — \`subagent({ agent: "fy-general-reviewer", task: "..." })\`.
|
|
65
|
+
Task prompt: implementer's report, diff base (the commit before this task's first commit — fixed across all review rounds; if unknown, find it with \`git log\`), diff head (current HEAD).
|
|
66
|
+
`
|
|
67
|
+
: "";
|
|
68
|
+
|
|
69
|
+
const body = `# Per-Task Gate — Round ${round}
|
|
70
|
+
|
|
71
|
+
Task "${safeTask}" is not ready to advance. Run the gates below, triage, then call task_ready_advance again.
|
|
72
|
+
${verifySection}${reviewSection}
|
|
73
|
+
#### Triage
|
|
74
|
+
- **Fixable** → fix it; count it (verifierIssuesFixed / reviewerIssuesFixed).
|
|
75
|
+
- **False-positive** → append to {{PI_FY_KNOWN_ISSUES_PATH}}.
|
|
76
|
+
- **Cannot-fix** (external blocker / missing dependency / breaking API contract) → escalate to the user.
|
|
77
|
+
|
|
78
|
+
Then call \`task_ready_advance({ verifierIssuesFixed: <count>, reviewerIssuesFixed: <count>${nextParam} })\` and follow its result — end your turn if it asks.
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
return `<skill name="fy-task-gate" location=" ">
|
|
82
|
+
${body}</skill>`;
|
|
83
|
+
}
|