@brainervirus/workit-core 0.10.0 → 0.11.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/package.json +1 -1
- package/src/core/flow-state.ts +284 -24
- package/templates/execution-contract.md +3 -3
- package/templates/plan-template.md +1 -1
- package/templates/superpowers-doc-contract.md +2 -2
- package/vendor/superpowers/skills/executing-plans/SKILL.md +2 -2
- package/vendor/superpowers/skills/subagent-driven-development/SKILL.md +2 -0
package/package.json
CHANGED
package/src/core/flow-state.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
unlinkSync,
|
|
14
14
|
writeFileSync,
|
|
15
15
|
} from "node:fs";
|
|
16
|
-
import { createHash } from "node:crypto";
|
|
16
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
17
17
|
import path from "node:path";
|
|
18
18
|
import { docsValidate, parseTasksFromPlan, qualitySpec, stripFences } from "./docs-validate";
|
|
19
19
|
import { resolveCanonicalLayout } from "./docs-layout";
|
|
@@ -64,6 +64,27 @@ export type FlowExecutionState = {
|
|
|
64
64
|
* every non-subagent-driven path and for legacy states without the field.
|
|
65
65
|
*/
|
|
66
66
|
coordinator_session_id: string | null;
|
|
67
|
+
/**
|
|
68
|
+
* Cursor delegation capability state (cursor-subagent-inline CA-01..CA-05):
|
|
69
|
+
* only SHA-256 hashes are persisted — never a raw lease or token. Cursor has
|
|
70
|
+
* no host-visible parentID, so an accepted Cursor `subagent-driven` menu
|
|
71
|
+
* choice stores a coordinator-lease hash and returns the raw lease ONCE;
|
|
72
|
+
* `mintDelegateToken` stores one task-scoped token hash and returns the raw
|
|
73
|
+
* token once. Optional and absent (never serialized) for OpenCode/CLI flows
|
|
74
|
+
* and legacy states so pre-delegation flow.json bytes stay stable.
|
|
75
|
+
*/
|
|
76
|
+
delegation?: FlowDelegationState | null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type FlowDelegationState = {
|
|
80
|
+
coordinator_lease_hash: string | null;
|
|
81
|
+
/** The task id the active token is bound to (one active token per flow). */
|
|
82
|
+
active_task_id: number | null;
|
|
83
|
+
token_hash: string | null;
|
|
84
|
+
/** Workspace root + slug the token hash is bound to (binding check). */
|
|
85
|
+
token_workspace: string | null;
|
|
86
|
+
token_slug: string | null;
|
|
87
|
+
status: "active" | "revoked";
|
|
67
88
|
};
|
|
68
89
|
|
|
69
90
|
/**
|
|
@@ -94,16 +115,6 @@ export const COORDINATOR_RECOVERY_TEXT =
|
|
|
94
115
|
"Delegate product mutations to an authenticated delegated worker via `task` / `wk-implement` instead of " +
|
|
95
116
|
"editing in the coordinator session.";
|
|
96
117
|
|
|
97
|
-
/**
|
|
98
|
-
* Cursor recovery guidance for the unsupported subagent-driven mutation path
|
|
99
|
-
* (CA-42): the Cursor MCP has no child sessions, so it cannot run a
|
|
100
|
-
* subagent-driven plan and must not enter that flow state.
|
|
101
|
-
*/
|
|
102
|
-
export const CURSOR_SUBAGENT_UNSUPPORTED_TEXT =
|
|
103
|
-
"Cursor cannot execute subagent-driven plans: the MCP has no child-session " +
|
|
104
|
-
"support. Choose Inline, Handoff, or a review option in this session, or " +
|
|
105
|
-
"run the plan in OpenCode with `wk-implement`.";
|
|
106
|
-
|
|
107
118
|
/**
|
|
108
119
|
* The only acceptable approval / execution-menu evidence (FG-04, CA-19, AR-12).
|
|
109
120
|
* Trust comes from HOST CAPABILITIES, never from caller-supplied fields:
|
|
@@ -278,19 +289,53 @@ const normalizeState = (parsed: unknown, slug: string): FlowState => {
|
|
|
278
289
|
mode: (execution.mode ?? null) as ExecutionMode | null,
|
|
279
290
|
evidence: (execution.evidence ?? null) as LifecycleEvidence | null,
|
|
280
291
|
coordinator_session_id: execution.coordinator_session_id ?? null,
|
|
292
|
+
// Optional field: legacy flow.json without the key stays byte-stable
|
|
293
|
+
// (no serialized `delegation` appears unless delegation state exists).
|
|
294
|
+
...(execution.delegation !== undefined
|
|
295
|
+
? { delegation: normalizeDelegation(execution.delegation) }
|
|
296
|
+
: {}),
|
|
281
297
|
},
|
|
282
298
|
handoff_destination: p.handoff_destination ?? false,
|
|
283
299
|
updated_at: p.updated_at ?? Date.now(),
|
|
284
300
|
};
|
|
285
301
|
};
|
|
286
302
|
|
|
303
|
+
const normalizeDelegation = (v: unknown): FlowDelegationState | null => {
|
|
304
|
+
if (v === null || v === undefined) return null;
|
|
305
|
+
if (!isRecord(v)) return null;
|
|
306
|
+
const hash = (x: unknown): string | null =>
|
|
307
|
+
typeof x === "string" && HEX64_RE.test(x) ? x : null;
|
|
308
|
+
return {
|
|
309
|
+
coordinator_lease_hash: hash(v.coordinator_lease_hash),
|
|
310
|
+
active_task_id:
|
|
311
|
+
typeof v.active_task_id === "number" && Number.isSafeInteger(v.active_task_id)
|
|
312
|
+
? v.active_task_id
|
|
313
|
+
: null,
|
|
314
|
+
token_hash: hash(v.token_hash),
|
|
315
|
+
token_workspace: typeof v.token_workspace === "string" ? v.token_workspace : null,
|
|
316
|
+
token_slug: typeof v.token_slug === "string" ? v.token_slug : null,
|
|
317
|
+
status: v.status === "active" ? "active" : "revoked",
|
|
318
|
+
};
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Cursor delegation persistence helper: raw lease/token values are hashed ONCE
|
|
322
|
+
// here and only the hex digest is ever placed on FlowState (CA-01 spec). The
|
|
323
|
+
// raw values are returned to the coordinator in tool results, never written.
|
|
324
|
+
const delegationHash = (raw: string): string => createHash("sha256").update(raw).digest("hex");
|
|
325
|
+
|
|
287
326
|
const emptyState = (slug: string): FlowState => ({
|
|
288
327
|
slug,
|
|
289
328
|
activated: false,
|
|
290
329
|
spec: { path: "", status: "draft", evidence: null, approved_digest: null },
|
|
291
330
|
plan: { path: "", status: "draft", evidence: null, approved_digest: null },
|
|
292
331
|
menu: { presented: false, chosen: "", evidence: null },
|
|
293
|
-
execution: {
|
|
332
|
+
execution: {
|
|
333
|
+
status: "pending",
|
|
334
|
+
mode: null,
|
|
335
|
+
evidence: null,
|
|
336
|
+
coordinator_session_id: null,
|
|
337
|
+
delegation: null,
|
|
338
|
+
},
|
|
294
339
|
handoff_destination: false,
|
|
295
340
|
updated_at: Date.now(),
|
|
296
341
|
});
|
|
@@ -478,6 +523,11 @@ const validateState = (
|
|
|
478
523
|
evidence: (execRaw?.evidence as LifecycleEvidence | null | undefined) ?? null,
|
|
479
524
|
coordinator_session_id:
|
|
480
525
|
(execRaw?.coordinator_session_id as string | null | undefined) ?? null,
|
|
526
|
+
// Optional field: legacy flow.json without the key stays byte-stable
|
|
527
|
+
// (no serialized `delegation` appears unless delegation state exists).
|
|
528
|
+
...(execRaw?.delegation !== undefined
|
|
529
|
+
? { delegation: normalizeDelegation(execRaw.delegation) }
|
|
530
|
+
: {}),
|
|
481
531
|
},
|
|
482
532
|
handoff_destination: parsed.handoff_destination ?? false,
|
|
483
533
|
updated_at: parsed.updated_at ?? Date.now(),
|
|
@@ -731,7 +781,13 @@ const resetForSpecDrift = (state: FlowState): FlowState => ({
|
|
|
731
781
|
spec: { ...state.spec, status: "draft", evidence: null, approved_digest: null },
|
|
732
782
|
plan: { ...state.plan, status: "draft", evidence: null, approved_digest: null },
|
|
733
783
|
menu: { presented: false, chosen: "", evidence: null },
|
|
734
|
-
execution: {
|
|
784
|
+
execution: {
|
|
785
|
+
status: "pending",
|
|
786
|
+
mode: null,
|
|
787
|
+
evidence: null,
|
|
788
|
+
coordinator_session_id: null,
|
|
789
|
+
...(state.execution.delegation !== undefined ? { delegation: null } : {}),
|
|
790
|
+
},
|
|
735
791
|
handoff_destination: false,
|
|
736
792
|
updated_at: Date.now(),
|
|
737
793
|
});
|
|
@@ -821,7 +877,12 @@ const deriveLegacyExecution = (
|
|
|
821
877
|
coordinator_session_id: null,
|
|
822
878
|
};
|
|
823
879
|
}
|
|
824
|
-
return {
|
|
880
|
+
return {
|
|
881
|
+
status: "pending",
|
|
882
|
+
mode: null,
|
|
883
|
+
evidence: null,
|
|
884
|
+
coordinator_session_id: null,
|
|
885
|
+
};
|
|
825
886
|
};
|
|
826
887
|
|
|
827
888
|
type CompatibilityResult = { state: FlowState; changed: boolean };
|
|
@@ -1605,7 +1666,12 @@ export const prepareFlowState = (
|
|
|
1605
1666
|
spec: { path: specPath, status: "draft", evidence: null, approved_digest: null },
|
|
1606
1667
|
plan: { path: planPath, status: "draft", evidence: null, approved_digest: null },
|
|
1607
1668
|
menu: { presented: false, chosen: "", evidence: null },
|
|
1608
|
-
execution: {
|
|
1669
|
+
execution: {
|
|
1670
|
+
status: "pending",
|
|
1671
|
+
mode: null,
|
|
1672
|
+
evidence: null,
|
|
1673
|
+
coordinator_session_id: null,
|
|
1674
|
+
},
|
|
1609
1675
|
handoff_destination: false,
|
|
1610
1676
|
updated_at: Date.now(),
|
|
1611
1677
|
});
|
|
@@ -1753,6 +1819,8 @@ export const transitionPlan = (
|
|
|
1753
1819
|
});
|
|
1754
1820
|
};
|
|
1755
1821
|
|
|
1822
|
+
export type MenuChoiceResult = { ok: true; coordinator_lease?: string } | FlowError;
|
|
1823
|
+
|
|
1756
1824
|
export const recordMenuChoice = (
|
|
1757
1825
|
root: string,
|
|
1758
1826
|
slug: string,
|
|
@@ -1760,7 +1828,7 @@ export const recordMenuChoice = (
|
|
|
1760
1828
|
choice: unknown,
|
|
1761
1829
|
evidence: unknown,
|
|
1762
1830
|
ctx?: MutationContext,
|
|
1763
|
-
):
|
|
1831
|
+
): MenuChoiceResult => {
|
|
1764
1832
|
const bound = assertMutationWorkspace(root, ctx);
|
|
1765
1833
|
if (!bound.ok) return bound;
|
|
1766
1834
|
const recorded = assertEvidenceShape(evidence);
|
|
@@ -1768,11 +1836,6 @@ export const recordMenuChoice = (
|
|
|
1768
1836
|
if (typeof choice !== "string" || !MENU_CHOICES.includes(choice as MenuChoice)) {
|
|
1769
1837
|
return err("menu_choice_invalid", `invalid menu choice: ${JSON.stringify(choice)}`);
|
|
1770
1838
|
}
|
|
1771
|
-
// Cursor cannot run subagent-driven plans (no child sessions): entering that
|
|
1772
|
-
// flow state on Cursor is rejected with recovery guidance (CA-42).
|
|
1773
|
-
if (recorded.evidence.host === "cursor" && choice === "subagent-driven") {
|
|
1774
|
-
return err("unsupported_mode", CURSOR_SUBAGENT_UNSUPPORTED_TEXT);
|
|
1775
|
-
}
|
|
1776
1839
|
// The execution-menu evidence must be the label the user selected on the
|
|
1777
1840
|
// native question; a mismatched choice is fabricated (FG-04). Comparison is
|
|
1778
1841
|
// case-insensitive: the host presents "Inline", the enum stores "inline"
|
|
@@ -1789,7 +1852,13 @@ export const recordMenuChoice = (
|
|
|
1789
1852
|
}
|
|
1790
1853
|
const doc = resolveDoc(root, slug, planPath, "plan");
|
|
1791
1854
|
if (!doc.ok) return err("path_invalid", doc.error);
|
|
1792
|
-
|
|
1855
|
+
// The Cursor coordinator lease is generated BEFORE the critical section so
|
|
1856
|
+
// the raw value is returned exactly once and only the hash crosses the
|
|
1857
|
+
// persisted-state boundary.
|
|
1858
|
+
const cursorSubagent = recorded.evidence.host === "cursor" && choice === "subagent-driven";
|
|
1859
|
+
const lease = cursorSubagent ? randomBytes(32).toString("hex") : null;
|
|
1860
|
+
const leaseHash = lease === null ? null : delegationHash(lease);
|
|
1861
|
+
const result = readModifyWrite(root, slug, (state) => {
|
|
1793
1862
|
if (state.spec.status !== "approved")
|
|
1794
1863
|
return err("spec_not_approved", "spec must be approved before the execution menu");
|
|
1795
1864
|
if (state.plan.status !== "approved")
|
|
@@ -1808,13 +1877,24 @@ export const recordMenuChoice = (
|
|
|
1808
1877
|
// pending. The menu evidence IS the lifecycle evidence — the choice the
|
|
1809
1878
|
// user selected on the native question. The activating OpenCode
|
|
1810
1879
|
// coordinator session (CA-12) is persisted ONLY for an accepted
|
|
1811
|
-
// subagent-driven activation; inline/handoff/review choices
|
|
1812
|
-
//
|
|
1880
|
+
// subagent-driven activation; inline/handoff/review choices keep it null.
|
|
1881
|
+
// Cursor's accepted subagent-driven path keeps it null too (no session
|
|
1882
|
+
// identity): delegation authority comes from the coordinator lease instead.
|
|
1813
1883
|
const executing = choice === "subagent-driven" || choice === "inline";
|
|
1814
1884
|
const coordinatorSessionId =
|
|
1815
1885
|
choice === "subagent-driven" && recorded.evidence.host === "opencode"
|
|
1816
1886
|
? (ctx?.sessionId ?? null)
|
|
1817
1887
|
: null;
|
|
1888
|
+
const delegation = cursorSubagent
|
|
1889
|
+
? {
|
|
1890
|
+
coordinator_lease_hash: leaseHash,
|
|
1891
|
+
active_task_id: null,
|
|
1892
|
+
token_hash: null,
|
|
1893
|
+
token_workspace: null,
|
|
1894
|
+
token_slug: null,
|
|
1895
|
+
status: "active" as const,
|
|
1896
|
+
}
|
|
1897
|
+
: state.execution.delegation;
|
|
1818
1898
|
return {
|
|
1819
1899
|
ok: true,
|
|
1820
1900
|
next: {
|
|
@@ -1830,17 +1910,197 @@ export const recordMenuChoice = (
|
|
|
1830
1910
|
mode: choice as ExecutionMode,
|
|
1831
1911
|
evidence: recorded.evidence,
|
|
1832
1912
|
coordinator_session_id: coordinatorSessionId,
|
|
1913
|
+
delegation,
|
|
1833
1914
|
}
|
|
1834
1915
|
: {
|
|
1835
1916
|
status: "pending",
|
|
1836
1917
|
mode: null,
|
|
1837
1918
|
evidence: recorded.evidence,
|
|
1838
1919
|
coordinator_session_id: null,
|
|
1920
|
+
delegation,
|
|
1839
1921
|
},
|
|
1840
1922
|
updated_at: Date.now(),
|
|
1841
1923
|
},
|
|
1842
1924
|
};
|
|
1843
1925
|
});
|
|
1926
|
+
if (!result.ok) return result;
|
|
1927
|
+
return cursorSubagent && lease !== null ? { ok: true, coordinator_lease: lease } : { ok: true };
|
|
1928
|
+
};
|
|
1929
|
+
|
|
1930
|
+
/**
|
|
1931
|
+
* Cursor delegation capability model (cursor-subagent-inline CA-01..CA-05):
|
|
1932
|
+
* the coordinator lease authorizes token minting; the task-scoped token
|
|
1933
|
+
* authorizes delegated mutations. Only hashes persist — raw values cross the
|
|
1934
|
+
* API boundary exactly once. Token lifecycle: one active token per flow,
|
|
1935
|
+
* reusable within its task, revoked by `revokeDelegateToken` when the task
|
|
1936
|
+
* progress line is recorded, replaced when the next task token mints.
|
|
1937
|
+
*/
|
|
1938
|
+
export type DelegateTokenResult = { ok: true; token: string } | FlowError;
|
|
1939
|
+
|
|
1940
|
+
export const mintDelegateToken = (
|
|
1941
|
+
root: string,
|
|
1942
|
+
slug: string,
|
|
1943
|
+
planPath: string,
|
|
1944
|
+
taskId: number,
|
|
1945
|
+
coordinatorLease: string,
|
|
1946
|
+
): DelegateTokenResult => {
|
|
1947
|
+
if (typeof taskId !== "number" || !Number.isSafeInteger(taskId) || taskId <= 0) {
|
|
1948
|
+
return err(
|
|
1949
|
+
"task_invalid",
|
|
1950
|
+
`task id must be a positive safe integer: ${JSON.stringify(taskId)}`,
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
if (typeof coordinatorLease !== "string" || coordinatorLease === "") {
|
|
1954
|
+
return err(
|
|
1955
|
+
"coordinator_lease_invalid",
|
|
1956
|
+
"a coordinator lease is required to mint a delegation token",
|
|
1957
|
+
);
|
|
1958
|
+
}
|
|
1959
|
+
const doc = resolveDoc(root, slug, planPath, "plan");
|
|
1960
|
+
if (!doc.ok) return err("path_invalid", doc.error);
|
|
1961
|
+
const token = randomBytes(32).toString("hex");
|
|
1962
|
+
const tokenHash = delegationHash(token);
|
|
1963
|
+
const result = readModifyWrite(root, slug, (state) => {
|
|
1964
|
+
const exec = state.execution;
|
|
1965
|
+
if (exec.status !== "active" || exec.mode !== "subagent-driven") {
|
|
1966
|
+
return err(
|
|
1967
|
+
"flow_not_active",
|
|
1968
|
+
"a delegation token requires an active subagent-driven execution",
|
|
1969
|
+
);
|
|
1970
|
+
}
|
|
1971
|
+
const delegation = exec.delegation;
|
|
1972
|
+
if (!delegation || delegation.status !== "active" || !delegation.coordinator_lease_hash) {
|
|
1973
|
+
return err(
|
|
1974
|
+
"coordinator_lease_invalid",
|
|
1975
|
+
"this flow has no active coordinator lease — record the execution menu with Cursor subagent-driven first",
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1978
|
+
if (delegation.coordinator_lease_hash !== delegationHash(coordinatorLease)) {
|
|
1979
|
+
return err(
|
|
1980
|
+
"coordinator_lease_invalid",
|
|
1981
|
+
"the supplied coordinator lease does not match the flow's recorded lease",
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1984
|
+
if (state.plan.status !== "approved") {
|
|
1985
|
+
return err("plan_not_approved", "plan must be approved before delegating a task");
|
|
1986
|
+
}
|
|
1987
|
+
let planText: string;
|
|
1988
|
+
try {
|
|
1989
|
+
planText = readFileSync(doc.path, "utf8");
|
|
1990
|
+
} catch {
|
|
1991
|
+
return err("plan_missing", `plan not found: ${planPath}`);
|
|
1992
|
+
}
|
|
1993
|
+
if (!parseTasksFromPlan(planText).some((t) => t.id === taskId)) {
|
|
1994
|
+
return err("task_invalid", `task ${taskId} does not exist in the approved plan`);
|
|
1995
|
+
}
|
|
1996
|
+
if (ledgerCompletion(root, slug).completed.includes(taskId)) {
|
|
1997
|
+
return err("task_completed", `task ${taskId} is already completed in the SDD ledger`);
|
|
1998
|
+
}
|
|
1999
|
+
return {
|
|
2000
|
+
ok: true,
|
|
2001
|
+
next: {
|
|
2002
|
+
...state,
|
|
2003
|
+
execution: {
|
|
2004
|
+
...exec,
|
|
2005
|
+
delegation: {
|
|
2006
|
+
...delegation,
|
|
2007
|
+
active_task_id: taskId,
|
|
2008
|
+
token_hash: tokenHash,
|
|
2009
|
+
token_workspace: root,
|
|
2010
|
+
token_slug: slug,
|
|
2011
|
+
status: "active",
|
|
2012
|
+
},
|
|
2013
|
+
},
|
|
2014
|
+
updated_at: Date.now(),
|
|
2015
|
+
},
|
|
2016
|
+
};
|
|
2017
|
+
});
|
|
2018
|
+
if (!result.ok) return result;
|
|
2019
|
+
return { ok: true, token };
|
|
2020
|
+
};
|
|
2021
|
+
|
|
2022
|
+
export type DelegateTokenContext = {
|
|
2023
|
+
slug: string;
|
|
2024
|
+
taskId: number;
|
|
2025
|
+
hostWorkspace: string;
|
|
2026
|
+
};
|
|
2027
|
+
export type DelegateTokenValidation = { ok: true; context: DelegateTokenContext } | FlowError;
|
|
2028
|
+
|
|
2029
|
+
export const validateDelegateToken = (root: string, token: string): DelegateTokenValidation => {
|
|
2030
|
+
if (typeof token !== "string" || token === "") {
|
|
2031
|
+
return err("delegation_token_invalid", "a delegation token is required");
|
|
2032
|
+
}
|
|
2033
|
+
const hash = delegationHash(token);
|
|
2034
|
+
let entries: string[] = [];
|
|
2035
|
+
try {
|
|
2036
|
+
entries = readdirSync(path.join(root, "docs"), { withFileTypes: true })
|
|
2037
|
+
.filter((e) => e.isDirectory())
|
|
2038
|
+
.map((e) => e.name);
|
|
2039
|
+
} catch {
|
|
2040
|
+
return err("delegation_token_invalid", `no flows exist under ${JSON.stringify(root)}`);
|
|
2041
|
+
}
|
|
2042
|
+
for (const slug of entries) {
|
|
2043
|
+
try {
|
|
2044
|
+
const state = readFlowState(root, slug);
|
|
2045
|
+
const delegation = state.execution.delegation;
|
|
2046
|
+
if (!delegation || !delegation.token_hash) continue;
|
|
2047
|
+
if (delegation.token_hash !== hash) continue;
|
|
2048
|
+
if (delegation.token_workspace !== root || delegation.token_slug !== slug) continue;
|
|
2049
|
+
if (
|
|
2050
|
+
delegation.status !== "active" ||
|
|
2051
|
+
state.execution.status !== "active" ||
|
|
2052
|
+
state.execution.mode !== "subagent-driven" ||
|
|
2053
|
+
delegation.active_task_id === null
|
|
2054
|
+
) {
|
|
2055
|
+
return err(
|
|
2056
|
+
"delegation_token_revoked",
|
|
2057
|
+
"the delegation token is no longer active for this flow",
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
return {
|
|
2061
|
+
ok: true,
|
|
2062
|
+
context: { slug, taskId: delegation.active_task_id, hostWorkspace: root },
|
|
2063
|
+
};
|
|
2064
|
+
} catch {
|
|
2065
|
+
// unreadable flow state: skip, never throw from validation
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
return err(
|
|
2069
|
+
"delegation_token_invalid",
|
|
2070
|
+
"the delegation token does not match any active flow token in this workspace",
|
|
2071
|
+
);
|
|
2072
|
+
};
|
|
2073
|
+
|
|
2074
|
+
export const revokeDelegateToken = (root: string, slug: string, taskId: number): FlowGateResult => {
|
|
2075
|
+
if (typeof taskId !== "number" || !Number.isSafeInteger(taskId) || taskId <= 0) {
|
|
2076
|
+
return err(
|
|
2077
|
+
"task_invalid",
|
|
2078
|
+
`task id must be a positive safe integer: ${JSON.stringify(taskId)}`,
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
return readModifyWrite(root, slug, (state) => {
|
|
2082
|
+
const exec = state.execution;
|
|
2083
|
+
const delegation = exec.delegation;
|
|
2084
|
+
if (
|
|
2085
|
+
!delegation ||
|
|
2086
|
+
delegation.status !== "active" ||
|
|
2087
|
+
delegation.active_task_id !== taskId ||
|
|
2088
|
+
!delegation.token_hash
|
|
2089
|
+
) {
|
|
2090
|
+
return err(
|
|
2091
|
+
"delegation_token_not_active",
|
|
2092
|
+
`no active delegation token for task ${taskId} in ${slug}`,
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
2095
|
+
return {
|
|
2096
|
+
ok: true,
|
|
2097
|
+
next: {
|
|
2098
|
+
...state,
|
|
2099
|
+
execution: { ...exec, delegation: { ...delegation, status: "revoked" } },
|
|
2100
|
+
updated_at: Date.now(),
|
|
2101
|
+
},
|
|
2102
|
+
};
|
|
2103
|
+
});
|
|
1844
2104
|
};
|
|
1845
2105
|
|
|
1846
2106
|
/**
|
|
@@ -24,10 +24,10 @@ This session is a handoff destination for a continued plan. The originating sess
|
|
|
24
24
|
- Working state, briefs, ledgers, and review diffs live only under gitignored `<SDD_DIR>` in `docs/<slug>/sdd/` and use `workit_sdd_*` tools.
|
|
25
25
|
- Use native `todowrite` for visible task state as well as the gitignored ledger.
|
|
26
26
|
- Use native `question` for branch/stash choices and guarded external mutations; call mutation tools only after approval with `confirmed: true` (grounded in the recorded NativeChoiceEvidence).
|
|
27
|
-
- Flow-tool confirmations are never agent-typed booleans and never caller-supplied evidence objects: on OpenCode the plugin records the user's native-`question` answer as a host-observed one-use receipt (`attested: true`, `callID`, `selectedLabel`, `recordedAt`) consumed by `workit_spec_approve` / `workit_plan_approve` / `workit_plan_menu` — no evidence argument exists, and delegated worker status comes from host session parentage (`parentID`), never a caller `role` field. On Cursor, confirmations are policy-only (`attested: false`)
|
|
27
|
+
- Flow-tool confirmations are never agent-typed booleans and never caller-supplied evidence objects: on OpenCode the plugin records the user's native-`question` answer as a host-observed one-use receipt (`attested: true`, `callID`, `selectedLabel`, `recordedAt`) consumed by `workit_spec_approve` / `workit_plan_approve` / `workit_plan_menu` — no evidence argument exists, and delegated worker status comes from host session parentage (`parentID`), never a caller `role` field. On Cursor, confirmations are policy-only (`attested: false`), Subagent-driven execution is supported through the one-time `coordinator_lease` and per-task `delegation_token` minted by `workit_delegate` (fail-closed validation; no `parentID` identity exists on Cursor), and Inline runs single-agent in the current session.
|
|
28
28
|
- Delegated authority is direct-child-only: a worker is the session whose host `parentID` exactly equals the activating coordinator's recorded `coordinator_session_id`; missing, mismatched, or multi-owner lineage fails closed with `delegation_lineage_denied`, and nested `opencode` launches are denied during active delegated work. An authorized child receives only the compact worker contract (execute the supplied brief, follow TDD, land one contiguous non-empty commit range, report results) — never coordinator guidance, `wk-implement`, or ledger management; coordinator bookkeeping via `workit_sdd_*` stays with the coordinator session.
|
|
29
29
|
- On Cursor, for every repository-scoped `workit_*` call, pass the active Cursor workspace as `workspace_root`; never rely on the MCP process default.
|
|
30
|
-
- Use native `task` with only the built-in `explore` and `general` agents.
|
|
30
|
+
- Use native `task` with only the built-in `explore` and `general` agents (OpenCode); on Cursor, Subagent-driven dispatches Cursor-native subagents with a task `delegation_token`, Inline runs single-agent in the current session.
|
|
31
31
|
|
|
32
32
|
## Flow gates (HARD)
|
|
33
33
|
|
|
@@ -51,7 +51,7 @@ For each top-level task absent from `completed_task_ids`:
|
|
|
51
51
|
|
|
52
52
|
1. Mark it `in_progress` with `todowrite`.
|
|
53
53
|
2. Create a working-state brief with `workit_sdd_task_brief` and `confirmed: true`.
|
|
54
|
-
3.
|
|
54
|
+
3. Route by the approved execution mode: **Subagent-driven** delegates read-only discovery to an `explore` (OpenCode) agent, delegates implementation to a fresh `general` (OpenCode) agent (on Cursor, the coordinator mints a `delegation_token` with `workit_delegate` and the subagent prompt carries the raw token — the worker passes it as `delegation_token` on its mutation calls, and appending progress revokes it), and the coordinator never edits product code; **Inline** executes every task in the current agent with no dispatch and no token minting. Product changes follow TDD.
|
|
55
55
|
4. Create a working-state diff with `workit_sdd_review_package` and `confirmed: true`.
|
|
56
56
|
5. Delegate spec-compliance review and code-quality review to separate `general` agents.
|
|
57
57
|
6. **Blocking** findings (Critical, Important, or spec-compliance) may trigger at most **two** fix+re-review rounds per task. **Advisory** findings (Minor, style, YAGNI, taste) never pause the loop — append them with `workit_sdd_append_advisory` (`--task <id> --text <text>`, `confirmed: true`) instead of an unrestricted file edit.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# <Feature> Implementation Plan
|
|
2
2
|
|
|
3
|
-
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. On Cursor the two paths are: **Subagent-driven** — Cursor-native subagents dispatched by the coordinator, each carrying a task-scoped `delegation_token` minted with `workit_delegate` from the one-time `coordinator_lease`; **Inline** — `executing-plans` in the current session, single-agent, no dispatch, no token minting. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
4
|
|
|
5
5
|
**Spec:** `docs/<slug>/spec.md`
|
|
6
6
|
**Branch:** `feature/<slug>`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Superpowers document contract
|
|
2
2
|
|
|
3
|
-
Use OpenCode's native `question` for every bounded user choice. Give concise choices and allow a custom answer; if the tool is unavailable, ask one concise plain-text question. Use `skill` to load workflow and Superpowers skills, `todowrite` for task state, and `task` for delegated work.
|
|
3
|
+
Use OpenCode's native `question` for every bounded user choice. Give concise choices and allow a custom answer; if the tool is unavailable, ask one concise plain-text question. Use `skill` to load workflow and Superpowers skills, `todowrite` for task state, and `task` for delegated work (OpenCode) or the host-native equivalent — on Cursor, Subagent-driven dispatches Cursor-native subagents with a task `delegation_token`, Inline runs single-agent in the current session.
|
|
4
4
|
|
|
5
5
|
## Tracked document layout
|
|
6
6
|
|
|
@@ -35,7 +35,7 @@ Before writing **Branch:** into a new spec or plan, call `workit_docs_branch` an
|
|
|
35
35
|
- Commits use `wk-commit` after its native `question` confirmation.
|
|
36
36
|
- Continuation uses `wk-handoff`, whose `workit_handoff_session` creates and seeds the OpenCode session automatically.
|
|
37
37
|
- Never use worktrees. Resolve the declared branch with `workit_resolve_branch`, preview dirty-tree stash choices with `question`, and apply an approved in-place checkout through `workit_branch_setup` with `confirmed: true` (grounded in the recorded NativeChoiceEvidence).
|
|
38
|
-
- Flow-tool confirmations are never agent-typed booleans and never caller-supplied evidence objects: on OpenCode the plugin records the user's native-`question` answer as a host-observed one-use receipt (`attested: true`, `callID`, `selectedLabel`, `recordedAt`) consumed by `workit_spec_approve` / `workit_plan_approve` / `workit_plan_menu` — no evidence argument exists, and delegated worker status comes from host session parentage (`parentID`), never a caller `role` field. On Cursor, confirmations are policy-only (`attested: false`)
|
|
38
|
+
- Flow-tool confirmations are never agent-typed booleans and never caller-supplied evidence objects: on OpenCode the plugin records the user's native-`question` answer as a host-observed one-use receipt (`attested: true`, `callID`, `selectedLabel`, `recordedAt`) consumed by `workit_spec_approve` / `workit_plan_approve` / `workit_plan_menu` — no evidence argument exists, and delegated worker status comes from host session parentage (`parentID`), never a caller `role` field. On Cursor, confirmations are policy-only (`attested: false`), Subagent-driven execution is supported through the one-time `coordinator_lease` and per-task `delegation_token` minted by `workit_delegate` (fail-closed validation; no `parentID` identity exists on Cursor), and Inline runs single-agent in the current session.
|
|
39
39
|
- Keep all SDD state under the gitignored `docs/<slug>/sdd/`; use `workit_sdd_context` and the registered `workit_sdd_*` tools.
|
|
40
40
|
- After implementation, use `question` before an approved stash reapply through `workit_branch_setup` with `confirmed: true`.
|
|
41
41
|
|
|
@@ -7,11 +7,11 @@ description: Use when you have a written implementation plan to execute in a sep
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
Load plan, review critically, execute all tasks, report when complete.
|
|
10
|
+
Load plan, review critically, execute all tasks in the current session, report when complete. This is the single-agent Inline execution path.
|
|
11
11
|
|
|
12
12
|
**Announce at start:** "I'm using the executing-plans skill to implement this plan."
|
|
13
13
|
|
|
14
|
-
**Note:** Tell your human partner that Superpowers works much better with access to subagents. The quality of its work will be significantly higher if run on a platform with subagent support (Claude Code, Codex CLI, Codex App, and Copilot CLI all qualify; see the per-platform tool refs in `../using-superpowers/references/`). If subagents are available, use superpowers:subagent-driven-development instead of this skill.
|
|
14
|
+
**Note:** This skill is the single-agent Inline execution path: every task runs in the current session — no token minting, no subagent dispatching. Tell your human partner that Superpowers works much better with access to subagents. The quality of its work will be significantly higher if run on a platform with subagent support (Claude Code, Codex CLI, Codex App, and Copilot CLI all qualify; see the per-platform tool refs in `../using-superpowers/references/`). If subagents are available and the plan's approved execution mode is Subagent-driven, use superpowers:subagent-driven-development instead of this skill.
|
|
15
15
|
|
|
16
16
|
## The Process
|
|
17
17
|
|
|
@@ -11,6 +11,8 @@ Execute plan by dispatching a fresh implementer subagent per task, a task review
|
|
|
11
11
|
|
|
12
12
|
**Core principle:** Fresh subagent per task + task review (spec + quality) + broad final review = high quality, fast iteration
|
|
13
13
|
|
|
14
|
+
On Cursor, the coordinator mints a task-scoped delegation token via `workit_delegate` (from the `workit_plan_menu` coordinator lease) and the subagent prompt must carry it as `delegation_token` for mutation calls.
|
|
15
|
+
|
|
14
16
|
**Narration:** between tool calls, narrate at most one short line — the
|
|
15
17
|
ledger and the tool results carry the record.
|
|
16
18
|
|