@brainervirus/workit-core 0.10.0 → 1.0.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/README.md +5 -3
- package/package.json +8 -3
- package/scripts/analyze-release-scope.ts +4 -1
- package/scripts/doctor-check.ts +3 -1
- package/scripts/install-codex-plugin.sh +28 -0
- package/scripts/install-cursor-plugin.sh +1 -0
- package/scripts/install-opencode-plugin.sh +1 -0
- package/scripts/install-pi-package.sh +56 -0
- package/scripts/rewrite-workspace-deps.ts +22 -5
- package/scripts/sync-release-manifests.ts +8 -5
- package/scripts/sync-runtime.sh +7 -3
- package/scripts/validate-cursor-marketplace.ts +16 -62
- package/skills/workit-babysit/SKILL.md +33 -0
- package/skills/workit-behavioral-tdd/SKILL.md +53 -0
- package/skills/workit-blast-radius/SKILL.md +31 -0
- package/skills/workit-challenge/SKILL.md +62 -0
- package/skills/workit-debug/SKILL.md +61 -0
- package/skills/workit-deslop/SKILL.md +36 -0
- package/skills/workit-diagram/SKILL.md +32 -0
- package/skills/workit-green-run/SKILL.md +29 -0
- package/skills/workit-handoff/SKILL.md +43 -0
- package/skills/workit-implement/SKILL.md +46 -0
- package/skills/workit-mockup/SKILL.md +28 -0
- package/skills/workit-plan/SKILL.md +66 -0
- package/skills/workit-review/SKILL.md +60 -0
- package/skills/workit-steer/SKILL.md +32 -0
- package/src/core/authority.ts +918 -0
- package/src/core/boundary.ts +29 -0
- package/src/core/branch.ts +26 -255
- package/src/core/changelog.ts +95 -18
- package/src/core/commit-flavors.ts +65 -0
- package/src/core/config-conversion.ts +183 -0
- package/src/core/config.ts +42 -0
- package/src/core/cutover.ts +721 -0
- package/src/core/detect-hosts.ts +107 -0
- package/src/core/doctor.ts +428 -18
- package/src/core/external-action-effects.ts +1171 -0
- package/src/core/external-action.ts +541 -0
- package/src/core/init.ts +83 -32
- package/src/core/methods.ts +125 -0
- package/src/core/policy-resolver.ts +523 -0
- package/src/core/pr-create.ts +7 -1
- package/src/core/registration.ts +69 -4
- package/src/core/repo-context.ts +59 -1
- package/src/core/setup.ts +28 -0
- package/src/core/skill-manifests.ts +40 -42
- package/src/core/support-matrix.ts +8 -3
- package/src/core/sync-runtime.ts +15 -3
- package/src/core/task-context.ts +179 -0
- package/src/core/task-contract.ts +1136 -0
- package/src/core/task-engine.ts +2562 -0
- package/src/core/task-evaluation.ts +679 -0
- package/src/core/task-store.ts +1071 -0
- package/src/core/tracker-issues.ts +237 -0
- package/src/core/uninstall.ts +67 -3
- package/src/core/vcs-config.ts +53 -22
- package/src/core/workers.ts +166 -0
- package/src/core/workspaces.ts +3 -1
- package/src/core/youtrack-tools.ts +2 -0
- package/src/core/youtrack.ts +145 -10
- package/src/core.ts +126 -0
- package/templates/execution-contract.md +17 -50
- package/templates/plan-template.md +3 -3
- package/templates/spec-template.md +15 -3
- package/templates/workit-contract.md +12 -0
- package/commands/wk-changelog.md +0 -2
- package/commands/wk-commit.md +0 -2
- package/commands/wk-docs-refresh.md +0 -2
- package/commands/wk-handoff.md +0 -2
- package/commands/wk-implement.md +0 -2
- package/commands/wk-init.md +0 -2
- package/commands/wk-issue-update.md +0 -2
- package/commands/wk-meetings.md +0 -2
- package/commands/wk-pr.md +0 -2
- package/commands/wk-release-notes.md +0 -2
- package/commands/wk-status.md +0 -2
- package/commands/wk-verify.md +0 -2
- package/scripts/update-superpowers.sh +0 -82
- package/scripts/vendor-assets.ts +0 -37
- package/skills/wk-changelog/SKILL.md +0 -15
- package/skills/wk-commit/SKILL.md +0 -16
- package/skills/wk-docs-refresh/SKILL.md +0 -15
- package/skills/wk-handoff/SKILL.md +0 -20
- package/skills/wk-implement/SKILL.md +0 -47
- package/skills/wk-init/SKILL.md +0 -31
- package/skills/wk-issue-update/SKILL.md +0 -27
- package/skills/wk-issue-update/references/youtrack-update-style.md +0 -81
- package/skills/wk-meetings/SKILL.md +0 -17
- package/skills/wk-pr/SKILL.md +0 -27
- package/skills/wk-release-notes/SKILL.md +0 -15
- package/skills/wk-status/SKILL.md +0 -16
- package/skills/wk-verify/SKILL.md +0 -16
- package/src/core/detector.ts +0 -239
- package/src/core/flow-state.ts +0 -3048
- package/src/core/handoff-context.ts +0 -136
- package/src/core/handoff-tools.ts +0 -133
- package/src/core/menu.ts +0 -70
- package/src/core/plan-tasks.ts +0 -43
- package/src/core/reminder.ts +0 -124
- package/src/core/sdd.ts +0 -353
- package/src/state.ts +0 -22
- package/templates/superpowers-doc-contract.md +0 -75
- package/vendor/superpowers/skills/brainstorming/SKILL.md +0 -159
- package/vendor/superpowers/skills/brainstorming/scripts/frame-template.html +0 -213
- package/vendor/superpowers/skills/brainstorming/scripts/helper.js +0 -167
- package/vendor/superpowers/skills/brainstorming/scripts/server.cjs +0 -723
- package/vendor/superpowers/skills/brainstorming/scripts/start-server.sh +0 -209
- package/vendor/superpowers/skills/brainstorming/scripts/stop-server.sh +0 -120
- package/vendor/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
- package/vendor/superpowers/skills/brainstorming/visual-companion.md +0 -291
- package/vendor/superpowers/skills/dispatching-parallel-agents/SKILL.md +0 -185
- package/vendor/superpowers/skills/executing-plans/SKILL.md +0 -70
- package/vendor/superpowers/skills/finishing-a-development-branch/SKILL.md +0 -241
- package/vendor/superpowers/skills/receiving-code-review/SKILL.md +0 -213
- package/vendor/superpowers/skills/requesting-code-review/SKILL.md +0 -103
- package/vendor/superpowers/skills/requesting-code-review/code-reviewer.md +0 -172
- package/vendor/superpowers/skills/subagent-driven-development/SKILL.md +0 -426
- package/vendor/superpowers/skills/subagent-driven-development/implementer-prompt.md +0 -139
- package/vendor/superpowers/skills/subagent-driven-development/scripts/review-package +0 -44
- package/vendor/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +0 -22
- package/vendor/superpowers/skills/subagent-driven-development/scripts/task-brief +0 -40
- package/vendor/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +0 -188
- package/vendor/superpowers/skills/systematic-debugging/CREATION-LOG.md +0 -119
- package/vendor/superpowers/skills/systematic-debugging/SKILL.md +0 -296
- package/vendor/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +0 -158
- package/vendor/superpowers/skills/systematic-debugging/condition-based-waiting.md +0 -115
- package/vendor/superpowers/skills/systematic-debugging/defense-in-depth.md +0 -122
- package/vendor/superpowers/skills/systematic-debugging/find-polluter.sh +0 -63
- package/vendor/superpowers/skills/systematic-debugging/root-cause-tracing.md +0 -169
- package/vendor/superpowers/skills/systematic-debugging/test-academic.md +0 -14
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-1.md +0 -58
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-2.md +0 -68
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-3.md +0 -69
- package/vendor/superpowers/skills/test-driven-development/SKILL.md +0 -371
- package/vendor/superpowers/skills/test-driven-development/testing-anti-patterns.md +0 -299
- package/vendor/superpowers/skills/using-git-worktrees/SKILL.md +0 -202
- package/vendor/superpowers/skills/using-superpowers/SKILL.md +0 -62
- package/vendor/superpowers/skills/using-superpowers/references/antigravity-tools.md +0 -23
- package/vendor/superpowers/skills/using-superpowers/references/codex-tools.md +0 -39
- package/vendor/superpowers/skills/using-superpowers/references/pi-tools.md +0 -16
- package/vendor/superpowers/skills/verification-before-completion/SKILL.md +0 -139
- package/vendor/superpowers/skills/writing-plans/SKILL.md +0 -174
- package/vendor/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +0 -49
- package/vendor/superpowers/skills/writing-skills/SKILL.md +0 -689
- package/vendor/superpowers/skills/writing-skills/anthropic-best-practices.md +0 -1150
- package/vendor/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -189
- package/vendor/superpowers/skills/writing-skills/graphviz-conventions.dot +0 -172
- package/vendor/superpowers/skills/writing-skills/persuasion-principles.md +0 -187
- package/vendor/superpowers/skills/writing-skills/render-graphs.js +0 -168
- package/vendor/superpowers/skills/writing-skills/testing-skills-with-subagents.md +0 -384
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { docsValidate } from "./docs-validate";
|
|
4
|
-
import { resolveBranch } from "./branch";
|
|
5
|
-
import { parseTasksFromPlan } from "./docs-validate";
|
|
6
|
-
|
|
7
|
-
const DOC_RE = /docs\/([A-Za-z0-9][A-Za-z0-9._-]*)\/(spec|plan)\.md/g;
|
|
8
|
-
|
|
9
|
-
const listMd = (dir: string): string[] => {
|
|
10
|
-
try {
|
|
11
|
-
return readdirSync(dir)
|
|
12
|
-
.filter((f) => f.endsWith(".md"))
|
|
13
|
-
.sort();
|
|
14
|
-
} catch {
|
|
15
|
-
return [];
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type Resolved = { spec: string; plan: string; source: string } | { error: string };
|
|
20
|
-
|
|
21
|
-
const extractMessagePaths = (message: string): string[] =>
|
|
22
|
-
[...new Set(message.match(DOC_RE) ?? [])].sort();
|
|
23
|
-
|
|
24
|
-
const resolveFromMessagePaths = (root: string, message: string): Resolved => {
|
|
25
|
-
const paths = extractMessagePaths(message);
|
|
26
|
-
if (paths.length === 0) return { error: "no paths" };
|
|
27
|
-
const slugs = [...new Set(paths.map((p) => p.split("/")[1]))];
|
|
28
|
-
if (slugs.length !== 1)
|
|
29
|
-
return { error: "multiple features in message — use exactly one docs/<slug>/ pair" };
|
|
30
|
-
const slug = slugs[0];
|
|
31
|
-
const plan = `docs/${slug}/plan.md`;
|
|
32
|
-
const spec = `docs/${slug}/spec.md`;
|
|
33
|
-
if (!existsSync(path.join(root, plan)) || !existsSync(path.join(root, spec))) {
|
|
34
|
-
return { error: `docs/${slug}/ must contain both plan.md and spec.md` };
|
|
35
|
-
}
|
|
36
|
-
return { spec, plan, source: "message_paths" };
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const resolveActivePair = (root: string): Resolved => {
|
|
40
|
-
const docsDir = path.join(root, "docs");
|
|
41
|
-
let best: { score: number; spec: string; plan: string; source: string } | null = null;
|
|
42
|
-
let entries: string[] = [];
|
|
43
|
-
try {
|
|
44
|
-
entries = readdirSync(docsDir);
|
|
45
|
-
} catch {
|
|
46
|
-
return { error: "no pair" };
|
|
47
|
-
}
|
|
48
|
-
for (const slug of entries) {
|
|
49
|
-
if (slug.startsWith(".")) continue;
|
|
50
|
-
const plan = path.join("docs", slug, "plan.md");
|
|
51
|
-
const spec = path.join("docs", slug, "spec.md");
|
|
52
|
-
if (!existsSync(path.join(root, plan)) || !existsSync(path.join(root, spec))) continue;
|
|
53
|
-
const score = Math.max(
|
|
54
|
-
statSync(path.join(root, spec)).mtimeMs,
|
|
55
|
-
statSync(path.join(root, plan)).mtimeMs,
|
|
56
|
-
);
|
|
57
|
-
if (
|
|
58
|
-
best === null ||
|
|
59
|
-
score > best.score ||
|
|
60
|
-
(score === best.score && slug < best.spec.split("/")[1])
|
|
61
|
-
) {
|
|
62
|
-
best = { score, spec, plan, source: "active_pair" };
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (best === null) return { error: "no pair" };
|
|
66
|
-
return { spec: best.spec, plan: best.plan, source: best.source };
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
export const resolveWorkflowPaths = (root: string, message: string): Resolved => {
|
|
70
|
-
const fromMessage = resolveFromMessagePaths(root, message);
|
|
71
|
-
if (!("error" in fromMessage)) return fromMessage;
|
|
72
|
-
if (fromMessage.error !== "no paths") return fromMessage;
|
|
73
|
-
const active = resolveActivePair(root);
|
|
74
|
-
if (!("error" in active)) return active;
|
|
75
|
-
|
|
76
|
-
const docsDir = path.join(root, "docs");
|
|
77
|
-
if (!existsSync(docsDir) || listMd(docsDir).length === 0) {
|
|
78
|
-
return { error: "no docs/<slug>/ features found under docs/" };
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
error:
|
|
82
|
-
"could not resolve spec and plan — mention docs/<slug>/plan.md or create docs/<slug>/{spec.md,plan.md}",
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export const buildHandoffContract = ({
|
|
87
|
-
root,
|
|
88
|
-
spec,
|
|
89
|
-
plan,
|
|
90
|
-
templatePath,
|
|
91
|
-
}: {
|
|
92
|
-
root: string;
|
|
93
|
-
spec: string;
|
|
94
|
-
plan: string;
|
|
95
|
-
templatePath: string;
|
|
96
|
-
}): { prompt: string } | { error: string } => {
|
|
97
|
-
const validated = docsValidate({ spec_path: spec, plan_path: plan, workspace_root: root });
|
|
98
|
-
if (validated.ok === false) {
|
|
99
|
-
return {
|
|
100
|
-
error: `docs validation failed\n${JSON.stringify({ ok: false, errors: validated.errors })}`,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const branchResolved = resolveBranch({ spec_path: spec, plan_path: plan, workspace_root: root });
|
|
104
|
-
if ("error" in branchResolved) return { error: branchResolved.error as string };
|
|
105
|
-
const branch = branchResolved.branch;
|
|
106
|
-
const slug = path.basename(path.dirname(plan));
|
|
107
|
-
const sddDir = `docs/${slug}/sdd`;
|
|
108
|
-
|
|
109
|
-
const planText = readFileSync(path.join(root, plan), "utf8");
|
|
110
|
-
const tasks = parseTasksFromPlan(planText);
|
|
111
|
-
const taskList = tasks.map((t) => `- Task ${t.id}: ${t.title}`).join("\n");
|
|
112
|
-
|
|
113
|
-
let contract: string;
|
|
114
|
-
try {
|
|
115
|
-
contract = readFileSync(templatePath, "utf8");
|
|
116
|
-
} catch {
|
|
117
|
-
return { error: "missing template templates/execution-contract.md" };
|
|
118
|
-
}
|
|
119
|
-
contract = contract
|
|
120
|
-
.replace(/<SPEC_PATH>/g, spec)
|
|
121
|
-
.replace(/<PLAN_PATH>/g, plan)
|
|
122
|
-
.replace(/<BRANCH>/g, branch)
|
|
123
|
-
.replace(/<SLUG>/g, slug)
|
|
124
|
-
.replace(/<SDD_DIR>/g, sddDir)
|
|
125
|
-
.replace(/<TASK_LIST>/g, taskList);
|
|
126
|
-
|
|
127
|
-
// Every generated destination contract carries the marker on its OWN LINE
|
|
128
|
-
// (CA-07): a canonical template that loses the sentinel — or embeds it
|
|
129
|
-
// mid-line — is a broken contract, not a handoff prompt. Anchored to the
|
|
130
|
-
// line start/end so a substring mid-line cannot satisfy the guard.
|
|
131
|
-
if (!/^<workflow-handoff-destination>true<\/workflow-handoff-destination>$/m.test(contract)) {
|
|
132
|
-
return { error: "handoff destination contract missing its destination marker" };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return { prompt: contract };
|
|
136
|
-
};
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { fail, ok, type Result } from "../core";
|
|
3
|
-
import { resolveWorkflowPaths, buildHandoffContract } from "./handoff-context";
|
|
4
|
-
import { assetRoot } from "./package-root";
|
|
5
|
-
import type { FlowGateResult } from "./flow-state";
|
|
6
|
-
|
|
7
|
-
type ApiResponse<T> = { data?: T; error?: unknown };
|
|
8
|
-
type ApiResult<T> = Promise<ApiResponse<T>>;
|
|
9
|
-
type ApiVoidResult = Promise<ApiResponse<void> | void>;
|
|
10
|
-
type ApiUnknownResult = Promise<ApiResponse<unknown> | void>;
|
|
11
|
-
|
|
12
|
-
export type HandoffClient = {
|
|
13
|
-
session: {
|
|
14
|
-
create(input: {
|
|
15
|
-
body: { title: string };
|
|
16
|
-
query: { directory: string };
|
|
17
|
-
}): ApiResult<{ id: string }>;
|
|
18
|
-
promptAsync(input: {
|
|
19
|
-
path: { id: string };
|
|
20
|
-
query: { directory: string };
|
|
21
|
-
body: { parts: [{ type: "text"; text: string }] };
|
|
22
|
-
}): ApiVoidResult;
|
|
23
|
-
};
|
|
24
|
-
tui: {
|
|
25
|
-
selectSession(input: {
|
|
26
|
-
body: { sessionID: string };
|
|
27
|
-
query: { directory: string };
|
|
28
|
-
}): ApiUnknownResult;
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type HandoffRequest = {
|
|
33
|
-
directory: string;
|
|
34
|
-
title: string;
|
|
35
|
-
prompt: string;
|
|
36
|
-
stay: boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Destination marking hook (CA-07): invoked only after `promptAsync` succeeds
|
|
39
|
-
* and before any optional selection. The adapter supplies the core
|
|
40
|
-
* `markHandoffDestination` binding so destination state is marked atomically
|
|
41
|
-
* after the child session is seeded — never before creation/seed succeed and
|
|
42
|
-
* never undone by a later selection failure.
|
|
43
|
-
*/
|
|
44
|
-
afterSeed?: () => FlowGateResult | Promise<FlowGateResult>;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
type HandoffData = {
|
|
48
|
-
sessionID?: string;
|
|
49
|
-
seeded?: boolean;
|
|
50
|
-
selected?: boolean;
|
|
51
|
-
stage?: "create" | "seed" | "mark" | "select";
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export const message = (error: unknown) =>
|
|
55
|
-
error instanceof Error
|
|
56
|
-
? error.message
|
|
57
|
-
: typeof error === "object" && error !== null && "message" in error
|
|
58
|
-
? String(error.message)
|
|
59
|
-
: String(error);
|
|
60
|
-
export const apiError = (response: ApiResponse<unknown> | void) => response?.error;
|
|
61
|
-
|
|
62
|
-
export async function handoffSession(
|
|
63
|
-
client: HandoffClient,
|
|
64
|
-
request: HandoffRequest,
|
|
65
|
-
): Promise<Result<HandoffData>> {
|
|
66
|
-
let sessionID: string | undefined;
|
|
67
|
-
try {
|
|
68
|
-
const created = await client.session.create({
|
|
69
|
-
body: { title: request.title },
|
|
70
|
-
query: { directory: request.directory },
|
|
71
|
-
});
|
|
72
|
-
if (apiError(created)) throw apiError(created);
|
|
73
|
-
sessionID = created.data?.id;
|
|
74
|
-
if (!sessionID) return fail("session create returned no ID", { stage: "create" });
|
|
75
|
-
} catch (error) {
|
|
76
|
-
return fail(message(error), { stage: "create" });
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
const seeded = await client.session.promptAsync({
|
|
81
|
-
path: { id: sessionID },
|
|
82
|
-
query: { directory: request.directory },
|
|
83
|
-
body: { parts: [{ type: "text", text: request.prompt }] },
|
|
84
|
-
});
|
|
85
|
-
if (apiError(seeded)) throw apiError(seeded);
|
|
86
|
-
} catch (error) {
|
|
87
|
-
return fail(message(error), { sessionID, seeded: false, selected: false, stage: "seed" });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (request.afterSeed) {
|
|
91
|
-
try {
|
|
92
|
-
const marked = await request.afterSeed();
|
|
93
|
-
if (!marked.ok) {
|
|
94
|
-
return fail(marked.error, { sessionID, seeded: true, selected: false, stage: "mark" });
|
|
95
|
-
}
|
|
96
|
-
} catch (error) {
|
|
97
|
-
return fail(message(error), { sessionID, seeded: true, selected: false, stage: "mark" });
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (request.stay) return ok({ sessionID, seeded: true, selected: false });
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
const selected = await client.tui.selectSession({
|
|
105
|
-
body: { sessionID },
|
|
106
|
-
query: { directory: request.directory },
|
|
107
|
-
});
|
|
108
|
-
if (apiError(selected)) throw apiError(selected);
|
|
109
|
-
if (selected?.data !== true) throw new Error("session selection unavailable");
|
|
110
|
-
return ok({ sessionID, seeded: true, selected: true });
|
|
111
|
-
} catch (error) {
|
|
112
|
-
return fail(message(error), { sessionID, seeded: true, selected: false, stage: "select" });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export type HandoffContextResult =
|
|
117
|
-
| { prompt: string; spec: string; plan: string; sdd: string }
|
|
118
|
-
| { error: string };
|
|
119
|
-
|
|
120
|
-
export const buildHandoffPrompt = (root: string, message: string): HandoffContextResult => {
|
|
121
|
-
const resolved = resolveWorkflowPaths(root, message);
|
|
122
|
-
if ("error" in resolved) return { error: resolved.error };
|
|
123
|
-
const templatePath = path.join(assetRoot(), "templates", "execution-contract.md");
|
|
124
|
-
const contract = buildHandoffContract({
|
|
125
|
-
root,
|
|
126
|
-
spec: resolved.spec,
|
|
127
|
-
plan: resolved.plan,
|
|
128
|
-
templatePath,
|
|
129
|
-
});
|
|
130
|
-
if ("error" in contract) return { error: contract.error };
|
|
131
|
-
const sdd = `docs/${path.basename(path.dirname(resolved.plan))}/sdd`;
|
|
132
|
-
return { prompt: contract.prompt, spec: resolved.spec, plan: resolved.plan, sdd };
|
|
133
|
-
};
|
package/src/core/menu.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Host-neutral post-plan menu wording and the handoff destination marker
|
|
6
|
-
* (CA-07, CA-08). Import-light by design: session-start hooks and reminder
|
|
7
|
-
* selection consume these constants WITHOUT dragging in the full flow-state
|
|
8
|
-
* graph (approval digests, verification, repo-context), so a read-only hook
|
|
9
|
-
* stays network-free by source inspection (RL-09/CA-25).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/** Display labels the source menu presents (CA-08). */
|
|
13
|
-
export const SOURCE_MENU_LABELS = [
|
|
14
|
-
"Subagent-driven",
|
|
15
|
-
"Inline",
|
|
16
|
-
"Handoff",
|
|
17
|
-
"Review spec first",
|
|
18
|
-
"Review plan first",
|
|
19
|
-
"Change model first",
|
|
20
|
-
] as const;
|
|
21
|
-
|
|
22
|
-
/** Display labels a marked destination presents — exactly five, no Handoff (CA-08). */
|
|
23
|
-
export const DESTINATION_MENU_LABELS = [
|
|
24
|
-
"Subagent-driven",
|
|
25
|
-
"Inline",
|
|
26
|
-
"Review spec first",
|
|
27
|
-
"Review plan first",
|
|
28
|
-
"Change model first",
|
|
29
|
-
] as const;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* The exact sentinel every generated destination contract carries on its own
|
|
33
|
-
* line (CA-07). Host-neutral by design (CA-10): OpenCode seeded sessions,
|
|
34
|
-
* Cursor copy/paste prompts, and CLI output all detect a destination by this
|
|
35
|
-
* marker alone — no session IDs, parent IDs, or host metadata.
|
|
36
|
-
*/
|
|
37
|
-
export const HANDOFF_DESTINATION_MARKER =
|
|
38
|
-
"<workflow-handoff-destination>true</workflow-handoff-destination>";
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Marked handoff destinations in a workspace (CA-07, CA-08): the persisted
|
|
42
|
-
* `handoff_destination: true` flag that `markHandoffDestination` sets atomically
|
|
43
|
-
* AFTER a genuine generated destination prompt succeeds. Hosts use this at
|
|
44
|
-
* session start to select the destination reminder (four choices, no Handoff)
|
|
45
|
-
* without session or parent IDs. The flag is cleared by approval-drift resets
|
|
46
|
-
* AND by completion (a completed flow is never a destination); only a new-flow
|
|
47
|
-
* `prepareFlowState` initializes it (false), and `markHandoffDestination` is
|
|
48
|
-
* the sole true-setter. Raw read only: a session-start hook must never
|
|
49
|
-
* reconcile or rewrite flow state, so a stale flag may read true until the
|
|
50
|
-
* next effective read resets it — safe bias toward destination wording.
|
|
51
|
-
* ponytail: raw flag scan, not readEffectiveFlowState — a hook is read-only;
|
|
52
|
-
* reconcile-on-drift is the mutation path's job.
|
|
53
|
-
*/
|
|
54
|
-
export const findMarkedDestinations = (root: string): string[] => {
|
|
55
|
-
const docsDir = path.join(root, "docs");
|
|
56
|
-
if (!existsSync(docsDir)) return [];
|
|
57
|
-
const slugs: string[] = [];
|
|
58
|
-
for (const slug of readdirSync(docsDir)) {
|
|
59
|
-
const file = path.join(docsDir, slug, "sdd", "flow.json");
|
|
60
|
-
if (!existsSync(file)) continue;
|
|
61
|
-
try {
|
|
62
|
-
const parsed = JSON.parse(readFileSync(file, "utf8")) as { handoff_destination?: unknown };
|
|
63
|
-
if (parsed.handoff_destination === true) slugs.push(slug);
|
|
64
|
-
} catch {
|
|
65
|
-
// a malformed flow.json excludes the entry without touching it
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return slugs;
|
|
70
|
-
};
|
package/src/core/plan-tasks.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { parseTasksFromPlan } from "./docs-validate";
|
|
4
|
-
import { resolveBranch } from "./branch";
|
|
5
|
-
|
|
6
|
-
const readSafe = (p: string): string | null => {
|
|
7
|
-
try {
|
|
8
|
-
return readFileSync(p, "utf8");
|
|
9
|
-
} catch {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export function parsePlanTasks(
|
|
15
|
-
planPath: string,
|
|
16
|
-
workspaceRoot: string,
|
|
17
|
-
):
|
|
18
|
-
| { task_count: number; tasks: { id: number; title: string; section_text: string }[] }
|
|
19
|
-
| { error: string } {
|
|
20
|
-
const cwd = path.resolve(workspaceRoot);
|
|
21
|
-
const resolved = path.isAbsolute(planPath) ? planPath : path.join(cwd, planPath);
|
|
22
|
-
const text = readSafe(resolved);
|
|
23
|
-
if (text === null) return { error: `plan file not found: ${planPath}` };
|
|
24
|
-
const tasks = parseTasksFromPlan(text);
|
|
25
|
-
if (tasks.length === 0) {
|
|
26
|
-
return { error: "no ### Task N sections found — plan must follow writing-plans format" };
|
|
27
|
-
}
|
|
28
|
-
return { task_count: tasks.length, tasks };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function resolveHandoffBranch(
|
|
32
|
-
specPath: string,
|
|
33
|
-
planPath: string,
|
|
34
|
-
workspaceRoot: string,
|
|
35
|
-
): { branch: string } | { error: string } {
|
|
36
|
-
const resolved = resolveBranch({
|
|
37
|
-
spec_path: specPath,
|
|
38
|
-
plan_path: planPath,
|
|
39
|
-
workspace_root: workspaceRoot,
|
|
40
|
-
});
|
|
41
|
-
if ("error" in resolved) return { error: resolved.error as string };
|
|
42
|
-
return { branch: resolved.branch };
|
|
43
|
-
}
|
package/src/core/reminder.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { DESTINATION_MENU_LABELS, HANDOFF_DESTINATION_MARKER, SOURCE_MENU_LABELS } from "./menu";
|
|
2
|
-
|
|
3
|
-
// Display-only source label list: the "(new session only)" qualifier appears in
|
|
4
|
-
// the reminder PROSE like the other source surfaces (bootstrap.ts, session-start,
|
|
5
|
-
// superpowers-doc-contract.md, ask-question-only.mdc), never in the machine
|
|
6
|
-
// label tuple `SOURCE_MENU_LABELS` — the receipt label must stay exactly
|
|
7
|
-
// `Handoff` for the native-question match (AR-12). Exported so contract tests
|
|
8
|
-
// assert the rendered reminder against this single source of truth instead of
|
|
9
|
-
// re-deriving the mapping.
|
|
10
|
-
export const SOURCE_MENU_LABELS_DISPLAY = SOURCE_MENU_LABELS.map((label) =>
|
|
11
|
-
label === "Handoff" ? "Handoff (new session only)" : label,
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
export const REMINDER_TEXT = `<workflow-contract-reminder>
|
|
15
|
-
- Bounded user choices → call the native \`question\` tool (never A/B/C or 1/2/3 lists in prose).
|
|
16
|
-
- After a plan is approved → native \`question\` menu with exactly: ${SOURCE_MENU_LABELS_DISPLAY.join(", ")}.
|
|
17
|
-
- Tools with \`confirmed\` → call them; never fabricate their result.
|
|
18
|
-
- Before the first \`workit_spec_approve\`/\`workit_plan_approve\` (self-review) run the superpowers writing-plans Self-Review checklist: spec coverage (every spec requirement maps to a task), placeholder scan, type consistency; fix findings inline.
|
|
19
|
-
- Delivering docs → clickable markdown link \`[spec.md](docs/<slug>/spec.md)\` + 3-5 bullet summary.
|
|
20
|
-
</workflow-contract-reminder>`;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Destination reminder (CA-07, CA-08): a marked handoff destination carries the
|
|
24
|
-
* exact marker and the four-label allow-list — never the originating Handoff
|
|
25
|
-
* choice. Hosts select source vs destination wording via `reminderTextFor`
|
|
26
|
-
* from `FlowState.handoff_destination`; the strings live here, not in adapters.
|
|
27
|
-
*/
|
|
28
|
-
export const DESTINATION_REMINDER_TEXT = `<workflow-contract-reminder>
|
|
29
|
-
- Bounded user choices → call the native \`question\` tool (never A/B/C or 1/2/3 lists in prose).
|
|
30
|
-
- This session is a handoff destination: present the post-plan menu with exactly: ${DESTINATION_MENU_LABELS.join(", ")}.
|
|
31
|
-
- Tools with \`confirmed\` → call them; never fabricate their result.
|
|
32
|
-
- Before the first \`workit_spec_approve\`/\`workit_plan_approve\` (self-review) run the superpowers writing-plans Self-Review checklist: spec coverage (every spec requirement maps to a task), placeholder scan, type consistency; fix findings inline.
|
|
33
|
-
- Delivering docs → clickable markdown link \`[spec.md](docs/<slug>/spec.md)\` + 3-5 bullet summary.
|
|
34
|
-
${HANDOFF_DESTINATION_MARKER}
|
|
35
|
-
</workflow-contract-reminder>`;
|
|
36
|
-
|
|
37
|
-
/** Select source vs destination reminder wording from the flow's destination flag (CA-08). */
|
|
38
|
-
export const reminderTextFor = (destination: boolean): string =>
|
|
39
|
-
destination ? DESTINATION_REMINDER_TEXT : REMINDER_TEXT;
|
|
40
|
-
|
|
41
|
-
export const DETECTION_TEXT = `<workflow-detection>
|
|
42
|
-
Your previous message presented choices as a numbered/bulleted list in prose.
|
|
43
|
-
That is a bounded user choice — use the native \`question\` tool instead (re-ask with \`question\` if still relevant).
|
|
44
|
-
</workflow-detection>`;
|
|
45
|
-
|
|
46
|
-
export const DOC_DELIVERY_TEXT = `<workflow-doc-delivery>
|
|
47
|
-
You referenced a doc with a backtick-only path. Deliver docs with a clickable markdown link \`[spec.md](docs/<slug>/spec.md)\` and a 3-5 bullet summary of the content.
|
|
48
|
-
</workflow-doc-delivery>`;
|
|
49
|
-
|
|
50
|
-
export const SDD_REMINDER_TEXT = `<workflow-sdd-reminder>
|
|
51
|
-
An approved plan is subagent-driven — execute it via \`wk-implement\` / \`task\` delegation. Never implement the approved plan inline in the main session.
|
|
52
|
-
</workflow-sdd-reminder>`;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Worker-only context (CA-16): an authorized direct child of the activating
|
|
56
|
-
* coordinator receives ONLY this compact contract — never the coordinator
|
|
57
|
-
* bootstrap or SDD_REMINDER_TEXT. It carries the worker duties (brief, TDD,
|
|
58
|
-
* commit range, report) and no coordination instructions.
|
|
59
|
-
*/
|
|
60
|
-
export const SDD_WORKER_REMINDER_TEXT = `<workflow-sdd-worker>
|
|
61
|
-
You are an authorized delegated worker for an active subagent-driven plan.
|
|
62
|
-
Execute only the supplied task brief: follow TDD (failing test first), land exactly one contiguous non-empty commit range for your task, then report status, commits, and test results to the coordinator. Do not manage coordinator bookkeeping or launch another agent harness.
|
|
63
|
-
</workflow-sdd-worker>`;
|
|
64
|
-
|
|
65
|
-
export const DOC_RENDER_TEXT = `<workflow-doc-render>
|
|
66
|
-
When delivering a spec or plan, by default render the full markdown content of the doc in chat (headings, tables, mermaid fences preserved) — NOT a backtick-wrapped raw block.
|
|
67
|
-
If the doc exceeds the render threshold (more than 150 lines, over 8KB, or more than 3 mermaid diagrams), deliver only the clickable link \`[spec.md](docs/<slug>/spec.md)\` + a 3-5 bullet summary.
|
|
68
|
-
On an explicit raw request ("raw", "para copiar", "sin render"), show the full fenced block instead.
|
|
69
|
-
Platform note: always use the standard \`\`\`mermaid fence — Cursor CLI renders it as an ASCII diagram, editors/GitHub render it natively; OpenCode TUI shows it as code text (renderer limitation, not a defect). Inline markdown links are not clickable in the OpenCode TUI.
|
|
70
|
-
</workflow-doc-render>`;
|
|
71
|
-
|
|
72
|
-
export const ISSUE_RAIL_TEXT = `<workflow-issue-rail>
|
|
73
|
-
A clickable \`question\` option whose label is an instruction (e.g. "Type the issue URL/ID") returns the label literal when clicked, not free text — ask for free text in plain prose with the custom answer field enabled instead.
|
|
74
|
-
</workflow-issue-rail>`;
|
|
75
|
-
|
|
76
|
-
export const CONFIG_GUARD_TEXT = `<workflow-config-guard>
|
|
77
|
-
A tool failed with a config-gap error (\`workflow config missing\`). Never configure without asking — ask with the native \`question\` tool, exactly three options: (1) configure only what's missing (guided, via the /wk-init skill flow for those actions), (2) run the full wizard (\`npx workit init\`), (3) skip — report the final error naming the missing items and how to configure them.
|
|
78
|
-
</workflow-config-guard>`;
|
|
79
|
-
|
|
80
|
-
export const VERIFICATION_TEXT = `<workflow-verification-rail>
|
|
81
|
-
Skill: verification-before-completion. NO completion claims without fresh verification evidence — run the check command (e.g. \`bun run check\` / \`workit_verify\`) and show its output before claiming done/fixed/passing. If you haven't run the verification command in this message, you cannot claim it passes.
|
|
82
|
-
</workflow-verification-rail>`;
|
|
83
|
-
|
|
84
|
-
export const TDD_TEXT = `<workflow-tdd-rail>
|
|
85
|
-
Skill: test-driven-development. NO production code without a failing test first — write the test, watch it fail, then write the minimal code to pass.
|
|
86
|
-
</workflow-tdd-rail>`;
|
|
87
|
-
|
|
88
|
-
export const BRAINSTORM_TEXT = `<workflow-brainstorm-rail>
|
|
89
|
-
Skill: brainstorming. NO implementation until a design is presented and approved — explore intent, ask clarifying questions one at a time, present the design, and get user approval before writing any code.
|
|
90
|
-
</workflow-brainstorm-rail>`;
|
|
91
|
-
|
|
92
|
-
export const DEBUG_TEXT = `<workflow-debug-rail>
|
|
93
|
-
Skill: systematic-debugging. NO fixes without root cause investigation first — read the error, reproduce consistently, find the root cause, then fix. Symptom fixes are failure.
|
|
94
|
-
</workflow-debug-rail>`;
|
|
95
|
-
|
|
96
|
-
export const REVIEW_RECEPTION_TEXT = `<workflow-review-reception-rail>
|
|
97
|
-
Skill: receiving-code-review. Verify before implementing — evaluate review feedback against codebase reality (read, restate, verify) before accepting or acting on it.
|
|
98
|
-
</workflow-review-reception-rail>`;
|
|
99
|
-
|
|
100
|
-
export const shouldInjectVerification = (currentText: string): boolean =>
|
|
101
|
-
!currentText.includes(VERIFICATION_TEXT);
|
|
102
|
-
|
|
103
|
-
export const shouldInjectTdd = (currentText: string): boolean => !currentText.includes(TDD_TEXT);
|
|
104
|
-
|
|
105
|
-
export const shouldInjectBrainstorm = (currentText: string): boolean =>
|
|
106
|
-
!currentText.includes(BRAINSTORM_TEXT);
|
|
107
|
-
|
|
108
|
-
export const shouldInjectDebug = (currentText: string): boolean =>
|
|
109
|
-
!currentText.includes(DEBUG_TEXT);
|
|
110
|
-
|
|
111
|
-
export const shouldInjectReviewReception = (currentText: string): boolean =>
|
|
112
|
-
!currentText.includes(REVIEW_RECEPTION_TEXT);
|
|
113
|
-
|
|
114
|
-
export const shouldInjectDocRender = (currentText: string): boolean =>
|
|
115
|
-
!currentText.includes(DOC_RENDER_TEXT);
|
|
116
|
-
|
|
117
|
-
export const shouldInjectSddReminder = (currentText: string): boolean =>
|
|
118
|
-
!currentText.includes(SDD_REMINDER_TEXT);
|
|
119
|
-
|
|
120
|
-
export const shouldInjectConfigGuard = (currentText: string): boolean =>
|
|
121
|
-
!currentText.includes(CONFIG_GUARD_TEXT);
|
|
122
|
-
|
|
123
|
-
export const shouldInjectIssueRail = (currentText: string): boolean =>
|
|
124
|
-
!currentText.includes(ISSUE_RAIL_TEXT);
|