@wrongstack/core 0.306.2 → 0.306.4
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/dist/coordination/index.js +184 -86
- package/dist/coordination/mail-tools.d.ts +1 -1
- package/dist/coordination/mailbox-project-server.js +38 -16
- package/dist/coordination/sqlite-mailbox.d.ts +1 -0
- package/dist/core/conversation-state.d.ts +1 -2
- package/dist/core/fallback-model.d.ts +19 -1
- package/dist/core/fallback-profile-manager.d.ts +21 -3
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +152 -46
- package/dist/defaults/index.js +347 -139
- package/dist/execution/auto-compaction-middleware.d.ts +64 -0
- package/dist/execution/compaction-core.d.ts +4 -0
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +234 -91
- package/dist/execution/retry-policy.d.ts +27 -0
- package/dist/hq/index.js +50 -15
- package/dist/index.d.ts +1 -1
- package/dist/index.js +642 -184
- package/dist/infrastructure/index.js +21 -0
- package/dist/plugin/index.js +195 -19
- package/dist/security/index.js +52 -16
- package/dist/security/kanban-boundary.d.ts +3 -1
- package/dist/session-catalog/index.js +50 -15
- package/dist/session-catalog/project-server.js +50 -15
- package/dist/storage/cloud-config-sync/sanitize.d.ts +18 -0
- package/dist/storage/cloud-config-sync.d.ts +1 -1
- package/dist/storage/index.js +326 -71
- package/dist/storage/provider-config-watcher.d.ts +9 -0
- package/dist/storage/session-store/strict-empty-check.d.ts +7 -0
- package/dist/storage/session-store.d.ts +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +83 -22
- package/dist/types/blocks.d.ts +9 -0
- package/dist/types/config/root.d.ts +14 -0
- package/dist/types/session.d.ts +7 -0
- package/instructions/agents/code-reviewer.md +3 -0
- package/instructions/coordination/subagent-baseline.md +10 -1
- package/instructions/system-lite.md +8 -5
- package/instructions/system-pro.md +26 -0
- package/instructions/system.md +21 -0
- package/package.json +3 -3
package/dist/tools/index.js
CHANGED
|
@@ -2024,6 +2024,12 @@ function normalizeModelRef(ref, defaultProvider) {
|
|
|
2024
2024
|
function hasText(value) {
|
|
2025
2025
|
return typeof value === "string" && value.trim().length > 0;
|
|
2026
2026
|
}
|
|
2027
|
+
function asRefList(value) {
|
|
2028
|
+
return Array.isArray(value) ? value : void 0;
|
|
2029
|
+
}
|
|
2030
|
+
function asProfileName(value) {
|
|
2031
|
+
return hasText(value) ? value : void 0;
|
|
2032
|
+
}
|
|
2027
2033
|
function providerHasKey(entry) {
|
|
2028
2034
|
if (!entry) return false;
|
|
2029
2035
|
if (hasText(entry.apiKey)) return true;
|
|
@@ -2034,7 +2040,7 @@ function providerHasKey(entry) {
|
|
|
2034
2040
|
}
|
|
2035
2041
|
function visibleProviderModels(config, providerId, providerModels) {
|
|
2036
2042
|
const entry = config.providers?.[providerId];
|
|
2037
|
-
return entry?.models
|
|
2043
|
+
return Array.isArray(entry?.models) ? [...entry.models] : providerModels;
|
|
2038
2044
|
}
|
|
2039
2045
|
function buildProfiles(config) {
|
|
2040
2046
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -2071,13 +2077,34 @@ var FallbackProfileManager = class {
|
|
|
2071
2077
|
listProfiles() {
|
|
2072
2078
|
return Object.freeze([...this.profiles.keys()]);
|
|
2073
2079
|
}
|
|
2080
|
+
/**
|
|
2081
|
+
* The profile the session has selected (`config.fallbackProfile`, set by
|
|
2082
|
+
* `/fallback profile use <name>`), or undefined when none is selected or the
|
|
2083
|
+
* name no longer resolves to a defined profile.
|
|
2084
|
+
*
|
|
2085
|
+
* Consulted by every resolution entry point when the caller does not name a
|
|
2086
|
+
* profile itself. Without this the leader — which passes no profile — could
|
|
2087
|
+
* never use a named profile at all: `config.fallbackProfiles` was reachable
|
|
2088
|
+
* only by copying a chain into `fallbackModels`.
|
|
2089
|
+
*/
|
|
2090
|
+
activeProfileName() {
|
|
2091
|
+
const name = asProfileName(this.config.fallbackProfile);
|
|
2092
|
+
return name && this.profiles.has(name) ? name : void 0;
|
|
2093
|
+
}
|
|
2074
2094
|
// ── Resolution ─────────────────────────────────────────────────────────
|
|
2075
2095
|
/**
|
|
2076
2096
|
* Resolve a named fallback profile to a validated, provider-filtered chain.
|
|
2077
2097
|
*
|
|
2078
|
-
* Returns an empty chain when
|
|
2079
|
-
*
|
|
2080
|
-
*
|
|
2098
|
+
* Returns an empty chain when the profile doesn't exist, or when every entry
|
|
2099
|
+
* is excluded, quarantined, or blacked out.
|
|
2100
|
+
*
|
|
2101
|
+
* Filtering is intentionally identical to {@link resolveRefs} (the explicit
|
|
2102
|
+
* `fallbackModels` path): the self-exclusion, the runtime status tracker,
|
|
2103
|
+
* and the availability calendar — nothing else. Anything a named profile
|
|
2104
|
+
* drops, an explicit chain drops too, and vice versa. Profiles used to apply
|
|
2105
|
+
* two extra filters (provider "usability" and the `providers[].models`
|
|
2106
|
+
* snapshot) that the explicit path did not, which silently rerouted roles
|
|
2107
|
+
* pinned to a profile onto a different model than the one configured.
|
|
2081
2108
|
*
|
|
2082
2109
|
* @param name - Profile name from config.fallbackProfiles.
|
|
2083
2110
|
* @param defaultProvider - Used when an entry has no explicit provider.
|
|
@@ -2098,13 +2125,9 @@ var FallbackProfileManager = class {
|
|
|
2098
2125
|
if (seen.has(key)) continue;
|
|
2099
2126
|
seen.add(key);
|
|
2100
2127
|
if (excludeKey && key === excludeKey) continue;
|
|
2101
|
-
const health = this.checkProvider(providerId);
|
|
2102
|
-
if (!health.usable) continue;
|
|
2103
2128
|
if (this.statusTracker && !this.statusTracker.isAvailable(providerId, parsed.model)) continue;
|
|
2104
2129
|
if (!evaluateModelCalendar(this.config.modelAvailabilitySchedule, providerId, parsed.model).allowed)
|
|
2105
2130
|
continue;
|
|
2106
|
-
const allowedModels = this.config.providers?.[providerId]?.models;
|
|
2107
|
-
if (allowedModels && !allowedModels.includes(parsed.model)) continue;
|
|
2108
2131
|
resolved.push({
|
|
2109
2132
|
providerId,
|
|
2110
2133
|
model: parsed.model,
|
|
@@ -2122,12 +2145,14 @@ var FallbackProfileManager = class {
|
|
|
2122
2145
|
resolveEffective(opts = {}) {
|
|
2123
2146
|
const bridge = this.resolveBridge(opts.exclude);
|
|
2124
2147
|
let selected = FREEZER_EMPTY;
|
|
2125
|
-
|
|
2126
|
-
|
|
2148
|
+
const explicitRefs = asRefList(opts.fallbackModels);
|
|
2149
|
+
const profileName = asProfileName(opts.fallbackProfile) ?? this.activeProfileName();
|
|
2150
|
+
if (explicitRefs && explicitRefs.length > 0) {
|
|
2151
|
+
const resolved = this.resolveRefs(explicitRefs, opts.exclude);
|
|
2127
2152
|
if (resolved.length > 0) selected = resolved;
|
|
2128
2153
|
}
|
|
2129
|
-
if (selected.length === 0 &&
|
|
2130
|
-
const resolved = this.resolve(
|
|
2154
|
+
if (selected.length === 0 && profileName) {
|
|
2155
|
+
const resolved = this.resolve(profileName, { exclude: opts.exclude });
|
|
2131
2156
|
if (resolved.length > 0) selected = resolved;
|
|
2132
2157
|
}
|
|
2133
2158
|
if (selected.length === 0 && opts.fallbackAuto !== false) {
|
|
@@ -2203,13 +2228,14 @@ var FallbackProfileManager = class {
|
|
|
2203
2228
|
};
|
|
2204
2229
|
const configFallbackAuto = this.config.fallbackAuto;
|
|
2205
2230
|
const effectiveFallbackAuto = configFallbackAuto !== void 0 && configFallbackAuto !== null ? configFallbackAuto : !opts.closedWorld;
|
|
2206
|
-
const explicitRefs = opts.fallbackModels ?? this.config.fallbackModels;
|
|
2231
|
+
const explicitRefs = asRefList(opts.fallbackModels) ?? asRefList(this.config.fallbackModels);
|
|
2232
|
+
const profileName = asProfileName(opts.fallbackProfile) ?? this.activeProfileName();
|
|
2207
2233
|
const explicitUsable = explicitRefs !== void 0 && explicitRefs.length > 0 && this.resolveRefs(explicitRefs, current).length > 0;
|
|
2208
|
-
const profileUsable =
|
|
2234
|
+
const profileUsable = profileName !== void 0 && this.hasProfile(profileName) && this.resolve(profileName, { exclude: current }).length > 0;
|
|
2209
2235
|
const fromExplicitSource = explicitUsable || profileUsable;
|
|
2210
|
-
const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? this.resolveRefs(explicitRefs, current) :
|
|
2236
|
+
const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? this.resolveRefs(explicitRefs, current) : profileName ? this.resolve(profileName, { exclude: current }) : FREEZER_EMPTY : this.resolveEffective({
|
|
2211
2237
|
fallbackModels: explicitRefs,
|
|
2212
|
-
fallbackProfile:
|
|
2238
|
+
fallbackProfile: profileName,
|
|
2213
2239
|
fallbackAuto: effectiveFallbackAuto,
|
|
2214
2240
|
exclude: current
|
|
2215
2241
|
});
|
|
@@ -2226,7 +2252,7 @@ var FallbackProfileManager = class {
|
|
|
2226
2252
|
});
|
|
2227
2253
|
}
|
|
2228
2254
|
candidates.push(...selectedChain);
|
|
2229
|
-
if (!fromExplicitSource && effectiveFallbackAuto &&
|
|
2255
|
+
if (!fromExplicitSource && effectiveFallbackAuto && profileName !== "default") {
|
|
2230
2256
|
candidates.push(...this.resolve("default", { exclude: current }));
|
|
2231
2257
|
}
|
|
2232
2258
|
if (!fromExplicitSource && effectiveFallbackAuto) {
|
|
@@ -2304,7 +2330,7 @@ var FallbackProfileManager = class {
|
|
|
2304
2330
|
const leaderModel = this.config.model;
|
|
2305
2331
|
const providers = this.config.providers ?? {};
|
|
2306
2332
|
const favoriteSet = new Set(
|
|
2307
|
-
(this.config.favoriteModels ?? []).map((ref) => {
|
|
2333
|
+
(asRefList(this.config.favoriteModels) ?? []).map((ref) => {
|
|
2308
2334
|
const p = parseModelRef(ref);
|
|
2309
2335
|
return `${p.provider ?? leaderProvider}/${p.model}`;
|
|
2310
2336
|
})
|
|
@@ -7478,6 +7504,30 @@ var PROVIDER_MANAGE_SCHEMA = {
|
|
|
7478
7504
|
required: ["action"],
|
|
7479
7505
|
additionalProperties: false
|
|
7480
7506
|
};
|
|
7507
|
+
var CREDENTIAL_SELECTOR_FIELDS = ["apiKey", "apiKeys", "activeKey", "envVars"];
|
|
7508
|
+
function envVarsClaimedByOtherProviders(providers, exceptProvider) {
|
|
7509
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
7510
|
+
for (const [id, entry] of Object.entries(providers)) {
|
|
7511
|
+
if (id === exceptProvider) continue;
|
|
7512
|
+
const names = entry?.["envVars"];
|
|
7513
|
+
if (!Array.isArray(names)) continue;
|
|
7514
|
+
for (const name of names) {
|
|
7515
|
+
if (typeof name === "string" && !claimed.has(name)) claimed.set(name, id);
|
|
7516
|
+
}
|
|
7517
|
+
}
|
|
7518
|
+
return claimed;
|
|
7519
|
+
}
|
|
7520
|
+
function rejectBorrowedEnvVars(providers, provider, requested) {
|
|
7521
|
+
if (!requested) return null;
|
|
7522
|
+
const claimed = envVarsClaimedByOtherProviders(providers, provider);
|
|
7523
|
+
for (const name of requested) {
|
|
7524
|
+
const owner = typeof name === "string" ? claimed.get(name) : void 0;
|
|
7525
|
+
if (owner !== void 0) {
|
|
7526
|
+
return `Environment variable "${String(name)}" already supplies the key for provider "${owner}". Reading another provider's credential from "${provider}" is not allowed; use provider_key_set to give "${provider}" its own key.`;
|
|
7527
|
+
}
|
|
7528
|
+
}
|
|
7529
|
+
return null;
|
|
7530
|
+
}
|
|
7481
7531
|
function validateProviderBaseUrl(raw) {
|
|
7482
7532
|
let url;
|
|
7483
7533
|
try {
|
|
@@ -7548,6 +7598,8 @@ ${msg}`,
|
|
|
7548
7598
|
const invalid = validateProviderBaseUrl(input.baseUrl);
|
|
7549
7599
|
if (invalid) return { status: "error", message: invalid };
|
|
7550
7600
|
}
|
|
7601
|
+
const borrowed = rejectBorrowedEnvVars(providers, input.provider, input.envVars);
|
|
7602
|
+
if (borrowed) return { status: "error", message: borrowed };
|
|
7551
7603
|
const entry = { type: input.type };
|
|
7552
7604
|
if (input.models) entry.models = input.models;
|
|
7553
7605
|
if (input.baseUrl) entry.baseUrl = input.baseUrl;
|
|
@@ -7581,14 +7633,22 @@ ${msg}`,
|
|
|
7581
7633
|
if (input.autoDiscoverModels !== void 0) entry.autoDiscoverModels = input.autoDiscoverModels;
|
|
7582
7634
|
if (input.apiKey !== void 0) entry.apiKey = input.apiKey || void 0;
|
|
7583
7635
|
const endpointChanged = input.baseUrl !== void 0 && (entry.baseUrl ?? void 0) !== (previous.baseUrl ?? void 0);
|
|
7584
|
-
const
|
|
7585
|
-
|
|
7636
|
+
const explicitlySupplied = /* @__PURE__ */ new Set([
|
|
7637
|
+
...input.apiKey !== void 0 ? ["apiKey"] : [],
|
|
7638
|
+
...input.envVars !== void 0 ? ["envVars"] : []
|
|
7639
|
+
]);
|
|
7640
|
+
const droppedFields = endpointChanged ? CREDENTIAL_SELECTOR_FIELDS.filter(
|
|
7641
|
+
(field) => !explicitlySupplied.has(field) && previous[field] !== void 0
|
|
7642
|
+
) : [];
|
|
7643
|
+
for (const field of droppedFields) entry[field] = void 0;
|
|
7644
|
+
const borrowed = rejectBorrowedEnvVars(providers, input.provider, input.envVars);
|
|
7645
|
+
if (borrowed) return { status: "error", message: borrowed };
|
|
7586
7646
|
providers[input.provider] = entry;
|
|
7587
7647
|
await opts.updateConfig((cfg) => {
|
|
7588
7648
|
cfg.providers = providers;
|
|
7589
7649
|
});
|
|
7590
7650
|
const updated = Object.keys({ ...entry }).filter((k) => k !== "apiKey").join(", ");
|
|
7591
|
-
const keyNote =
|
|
7651
|
+
const keyNote = droppedFields.length > 0 ? ` \u2014 cleared ${droppedFields.join(", ")} because the base URL changed; set the key again with provider_key_set` : "";
|
|
7592
7652
|
return { status: "ok", message: `\u2713 Updated ${input.provider}: ${updated}${keyNote}` };
|
|
7593
7653
|
}
|
|
7594
7654
|
if (input.action === "remove") {
|
|
@@ -9494,6 +9554,7 @@ export {
|
|
|
9494
9554
|
createMcpControlTool,
|
|
9495
9555
|
createMcpUseTool,
|
|
9496
9556
|
createOneShotLLMTool,
|
|
9497
|
-
createPluginManagerTool
|
|
9557
|
+
createPluginManagerTool,
|
|
9558
|
+
validateProviderBaseUrl
|
|
9498
9559
|
};
|
|
9499
9560
|
//# sourceMappingURL=index.js.map
|
package/dist/types/blocks.d.ts
CHANGED
|
@@ -53,6 +53,15 @@ export interface ToolResultBlock {
|
|
|
53
53
|
* to the provider (it's an internal extension, not part of the wire format).
|
|
54
54
|
*/
|
|
55
55
|
_toolErrorInfo?: import('./tool.js').ToolErrorInfo | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Structured Kanban denial details for runtime callers and observability.
|
|
58
|
+
* Provider adapters deliberately serialize only the human-facing `content`.
|
|
59
|
+
*/
|
|
60
|
+
_kanbanBoundary?: (import('@wrongstack/kanban').KanbanBoundaryEvaluation & {
|
|
61
|
+
boardId?: string | undefined;
|
|
62
|
+
taskId?: string | undefined;
|
|
63
|
+
readinessIssues?: import('@wrongstack/kanban').KanbanContractReadinessIssue[] | undefined;
|
|
64
|
+
}) | undefined;
|
|
56
65
|
}
|
|
57
66
|
export interface ImageBlock {
|
|
58
67
|
type: 'image';
|
|
@@ -115,6 +115,20 @@ export interface Config {
|
|
|
115
115
|
* model by `/setmodel`, while the whole ordered list is used for failover.
|
|
116
116
|
*/
|
|
117
117
|
fallbackProfiles?: Record<string, string[]> | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* The named profile from {@link Config.fallbackProfiles} currently selected
|
|
120
|
+
* for failover, set by `/fallback profile use <name>` and cleared by
|
|
121
|
+
* `/fallback profile none`.
|
|
122
|
+
*
|
|
123
|
+
* Resolution order is `fallbackModels` → this profile → smart default, so an
|
|
124
|
+
* explicit chain still wins. Selecting a profile does NOT overwrite
|
|
125
|
+
* `fallbackModels`: before this field existed, `profile use` was implemented
|
|
126
|
+
* by copying the profile's entries over the explicit chain, which destroyed
|
|
127
|
+
* whatever the user had configured and left no way back.
|
|
128
|
+
*
|
|
129
|
+
* A name that no longer matches a defined profile is ignored.
|
|
130
|
+
*/
|
|
131
|
+
fallbackProfile?: string | undefined;
|
|
118
132
|
/**
|
|
119
133
|
* When `true` (the default) and `fallbackModels` is empty, a fallback chain
|
|
120
134
|
* is derived automatically from the other keyed providers/models so 429s
|
package/dist/types/session.d.ts
CHANGED
|
@@ -594,6 +594,13 @@ export interface SessionStore {
|
|
|
594
594
|
* Returns the refreshed summary. Throws if the session does not exist.
|
|
595
595
|
*/
|
|
596
596
|
rename(id: string, name: string): Promise<SessionSummary>;
|
|
597
|
+
/**
|
|
598
|
+
* Return true only when the persisted journal is strictly readable and
|
|
599
|
+
* contains lifecycle envelope events but no messages or other session content.
|
|
600
|
+
* Implementations should fail closed (false) for malformed or unknown events.
|
|
601
|
+
* Optional stores that cannot make this guarantee must omit the method.
|
|
602
|
+
*/
|
|
603
|
+
isEmpty?(id: string): Promise<boolean>;
|
|
597
604
|
delete(id: string): Promise<void>;
|
|
598
605
|
/**
|
|
599
606
|
* Rewrite the session JSONL file to contain only a fresh session_start
|
|
@@ -6,6 +6,9 @@ Scope:
|
|
|
6
6
|
- Review a diff for correctness bugs, edge cases, and regressions first
|
|
7
7
|
- Check error handling, resource cleanup, and concurrency hazards
|
|
8
8
|
- Assess readability, naming, and adherence to project conventions
|
|
9
|
+
- Flag cost-ladder violations: code that re-implements what the repo, the
|
|
10
|
+
language, the platform, or an installed dependency already provides; an
|
|
11
|
+
abstraction with a single caller; a new dependency bought for a few lines
|
|
9
12
|
- Separate must-fix from nice-to-have
|
|
10
13
|
|
|
11
14
|
Input format you accept:
|
|
@@ -11,6 +11,11 @@ self-contained handoff; do not take over fleet orchestration.
|
|
|
11
11
|
project's existing conventions, tests, and tooling.
|
|
12
12
|
- Make only task-relevant changes. Preserve unrelated work and avoid broad
|
|
13
13
|
refactors, dependency changes, generated churn, or formatting noise.
|
|
14
|
+
- Reach for new code last (the cost ladder). Prefer deleting over adding, reuse
|
|
15
|
+
what the repo, the language, the platform, or an installed dependency already
|
|
16
|
+
provides, and write it yourself only when none of them fit — then write the
|
|
17
|
+
smallest version. A new dependency needs an explicit grant. The ladder trims
|
|
18
|
+
code you invented; it never shrinks the assigned deliverable.
|
|
14
19
|
- Routine project-local reads, edits, and verification are pre-authorized when
|
|
15
20
|
the task permits implementation. Review, research, diagnosis, and planning
|
|
16
21
|
assignments remain read-only.
|
|
@@ -55,7 +60,11 @@ Execute the assigned task yourself; subagents do not orchestrate other workers.
|
|
|
55
60
|
|
|
56
61
|
If the task is too large, finish a clean and useful checkpoint. Submit
|
|
57
62
|
`completion:"partial"` with a concrete `remaining_work` description that a
|
|
58
|
-
fresh worker can execute.
|
|
63
|
+
fresh worker can execute. The same applies when verification refuses the same
|
|
64
|
+
work twice: stop retrying, and report `completion:"partial"` naming what was
|
|
65
|
+
refused and what the work still needs. A third identical attempt is never the
|
|
66
|
+
answer, and a refusal you cannot clear is a result to report, not a reason to
|
|
67
|
+
loop or to claim success. If an independent helper would materially improve
|
|
59
68
|
the outcome, ask the Director through the mailbox control-plane route with the
|
|
60
69
|
exact helper task, why it is independent, and the required output; continue
|
|
61
70
|
your own slice unless blocked.
|
|
@@ -14,11 +14,13 @@ The user is an experienced developer; accelerate them and stay focused.
|
|
|
14
14
|
5. Prefer surgical edits over rewrites.
|
|
15
15
|
6. Do not change unrelated code.
|
|
16
16
|
7. Match the file's existing conventions; add a dependency only when the task requires it.
|
|
17
|
-
8.
|
|
18
|
-
9.
|
|
19
|
-
10.
|
|
20
|
-
11.
|
|
21
|
-
12.
|
|
17
|
+
8. The cost ladder — before writing new code, stop at the first rung that answers: can it be deleted instead; does it need to exist; does this repo already do it; does the language, runtime, or platform do it; does an installed dependency do it; is it one line? Only then write the minimum that works.
|
|
18
|
+
9. The ladder trims code you invented, never the user's request. Reuse claims need a named file, symbol, or package — not recollection. Do not narrate rung numbers.
|
|
19
|
+
10. Do not claim checks passed unless you ran them.
|
|
20
|
+
11. Separate verified facts from assumptions and unknowns.
|
|
21
|
+
12. An empty search result is an answer — adjust the query instead of repeating the identical call.
|
|
22
|
+
13. Keep responses concise and scannable.
|
|
23
|
+
14. Match the user's language.
|
|
22
24
|
|
|
23
25
|
## Working loop
|
|
24
26
|
|
|
@@ -69,6 +71,7 @@ These apply to what you write on the board, not to whether you may work; none is
|
|
|
69
71
|
3. **Keep the board current as you go.** Record the transition, comment, check result or link on the card itself, not only in chat, as the work happens. Do not leave finished work sitting in Running. Updating the card follows the action; it does not authorize it.
|
|
70
72
|
4. **Managed boards have a fixed column order.** Cards move `Backlog → Todo → Running → Review → Done`, one step at a time. If a transition is refused, the message names the field it wants — supply it and retry, or use the `kanban` action `release_managed_lifecycle` to return the board to plain tracking (cards and history are kept).
|
|
71
73
|
5. **Never shrink tracked scope by omission.** Todo, task, and plan rows carry Kanban requirement identity. Preserve every unfinished row and binding in full-list updates, and complete it before removal.
|
|
74
|
+
6. **Two refusals park the card — they never park you.** Verification guards Done, not progress. The board counts each refusal and parks the card at the second one; read the recorded reason, then fix exactly what it names or move to the next ready card. Never retry a parked card unchanged. Parking is durable and honest — not Done, not abandoned, and never a way to shed scope.
|
|
72
75
|
|
|
73
76
|
<!--ws:end-->
|
|
74
77
|
|
|
@@ -108,6 +108,27 @@ Reasoning depth is a dial, not a constant. Match it to the blast radius of what
|
|
|
108
108
|
11. **Leave the knowledge behind, not just the diff.** A task that taught you something durable about this codebase isn't finished until that knowledge is in memory (see Memory management).
|
|
109
109
|
12. **Keep helper scripts temporary and contained.** This rule applies to every agent, regardless of role (leader, coordinator, or subagent). Create all ad hoc helper scripts and their temporary inputs/outputs only under `<project-root>/.temp_files/` — never in the repository root or source directories. Write each helper script so its paths, imports, and generated artifacts work from that location. Delete the helper script and any temporary artifacts it created as soon as they are no longer needed, and always before reporting the task complete. Only remove files created for the current task; never delete pre-existing or user-owned contents of `.temp_files/`. This rule does not apply to permanent project scripts explicitly requested by the user.
|
|
110
110
|
|
|
111
|
+
## The cost ladder
|
|
112
|
+
|
|
113
|
+
The five questions above decide *whether the change is right*. This ladder decides *how much code it costs*. Before you write any new code — a function, a wrapper, a flag, a fallback path, a file — walk it in order and stop at the first rung that answers. Each rung down costs more to write, review, test, document, and eventually delete. The cheapest code in this repository is the code you did not write; the second cheapest is the code you deleted.
|
|
114
|
+
|
|
115
|
+
0. **Delete instead?** If removing code satisfies the request, that is the change. A net-negative diff that still passes is the best outcome available. Limit: delete what you have *read and understood*, never what merely looks unused — an unreferenced symbol may be reached by dynamic dispatch, a plugin, a test fixture, or a published entry point.
|
|
116
|
+
1. **Does it need to exist?** No speculative generality: no options object with one caller, no interface with one implementation, no config flag nobody asked for, no guard against a state that cannot occur, no error path for an error the type system already excludes. An abstraction earns its keep at the third caller, not the first — until then, duplication is cheaper than the wrong shape.
|
|
117
|
+
2. **Does this repo already do it?** Reuse it even when yours would be nicer — a second implementation of one idea is a bug that hasn't happened yet, because only one of the two will get the next fix. If the existing one is close but wrong, fix it in place and update its callers instead of forking it.
|
|
118
|
+
<!--ws:if tool=codebase-search-->
|
|
119
|
+
Answer this rung with `codebase-search` rather than recollection.
|
|
120
|
+
<!--ws:end-->
|
|
121
|
+
<!--ws:if tool=detect_duplicate_code-->
|
|
122
|
+
For a change that adds a sizable helper, `detect_duplicate_code` tells you whether you just re-invented one.
|
|
123
|
+
<!--ws:end-->
|
|
124
|
+
3. **Does the language or runtime do it?** Standard library and built-ins before hand-rolled utilities.
|
|
125
|
+
4. **Does the platform do it?** The OS, shell, filesystem, terminal, or browser already implements most of what a utility module would — and its version handles the edge cases yours will not.
|
|
126
|
+
5. **Does an installed dependency do it?** Read the manifest before reaching outward. A package already in the tree is free; a new one costs install size, audit surface, upgrade work, and a licence question.
|
|
127
|
+
6. **Is it one line?** Then it is one line: no helper, no wrapper, no abstraction layer around it, no options bag, no barrel re-export.
|
|
128
|
+
7. **Only now, write the minimum that works** — the smallest thing that satisfies the stated requirement and its verification target, in the surrounding file's idiom.
|
|
129
|
+
|
|
130
|
+
**Guardrails.** The ladder trims what **you** invented; it never shrinks what the user asked for — rung 1 is not a licence to deliver less than the request. If you believe the request itself is unnecessary, say so in one sentence and build it anyway. Rungs 2–5 need evidence, not recollection: name the file, symbol, or package you are reusing, because "I think we have something like that" is rung 7 in disguise. A new dependency is the user's decision, proposed with the reason and the alternative you rejected — never installed as a side effect. Run the ladder silently: report the change, not which rung you stopped at, unless the user asks.
|
|
131
|
+
|
|
111
132
|
<!--ws:if tool=todo-->
|
|
112
133
|
## Todo status lifecycle
|
|
113
134
|
|
|
@@ -154,6 +175,11 @@ These apply to what you write on the board, not to whether you may work. They ex
|
|
|
154
175
|
3. **Keep the board current as you go.** Move a card to Running when you actually start it, to Review when the work is done, and to Done once accepted; record the transition, comment, check result or link on the card itself rather than only in chat. Update it as the work happens instead of reconstructing it afterwards, and do not leave finished work sitting in Running. Updating the card follows the action; it does not authorize it.
|
|
155
176
|
4. **Managed boards have a fixed column order.** On a board in managed mode, cards move `Backlog → Todo → Running → Review → Done`, one step at a time. If a transition is refused, the message names the field or action it wants — supply that and retry. If the ceremony is not serving this work, the `kanban` action `release_managed_lifecycle` returns the board to plain tracking; cards and history are kept.
|
|
156
177
|
5. **Never shrink tracked scope by omission.** Todo, task, and plan rows are identity-bearing projections of Kanban requirements, not disposable prose. Keep every unfinished row and its board/task binding in full-list updates; complete it before removal.
|
|
178
|
+
6. **Two refusals park the card — they never park you.** Verification guards *Done*, not *progress*. This is the task-level form of the rule you already apply to tools: two failures in the same place mean your model is wrong, so a third identical attempt is not the answer. Every refusal from the completion gate or a `done` transition is counted on the card, and at the second one the board parks it and records what was refused. You do not park a card by hand and you do not argue with the gate: read the recorded reason, then either fix the exact thing it names or move to the next ready card. A parked card is an honest durable state — not Done, not abandoned, not a reason to stop working. Return to it when its blocker clears or when nothing else is ready.
|
|
179
|
+
|
|
180
|
+
Parking records that a card needs something you do not have; it never sheds scope. A criterion that turned out not to apply is a `remove_check`, not a park, and re-running an unchanged card to burn its budget is worse than reporting the refusal. If every remaining card is parked, say so plainly instead of reporting the work complete; a board of parked cards is a result the user needs to see, not a failure to hide.
|
|
181
|
+
|
|
182
|
+
A card waiting on a parked dependency is blocked for a real reason. Two honest moves exist and you must name which you took: clear the parked card, or correct the `dependsOn` because the dependency should never have been recorded. Silently working around a parked dependency is neither.
|
|
157
183
|
|
|
158
184
|
## Kanban scenarios and lifecycle
|
|
159
185
|
|
package/instructions/system.md
CHANGED
|
@@ -49,6 +49,24 @@ This parse is **internal reasoning**, not something you output. It keeps you anc
|
|
|
49
49
|
9. **The working tree is shared.** Never commit, push, amend, or discard changes unless the user asked for it. Treat destructive commands (recursive delete, hard reset, force push, history rewrites) as requiring an explicit request — never run them as convenience cleanup.
|
|
50
50
|
10. **Keep helper scripts temporary and contained.** This rule applies to every agent, regardless of role (leader, coordinator, or subagent). Create all ad hoc helper scripts and their temporary inputs/outputs only under `<project-root>/.temp_files/` — never in the repository root or source directories. Write each helper script so its paths, imports, and generated artifacts work from that location. Delete the helper script and any temporary artifacts it created as soon as they are no longer needed, and always before reporting the task complete. Only remove files created for the current task; never delete pre-existing or user-owned contents of `.temp_files/`. This rule does not apply to permanent project scripts explicitly requested by the user.
|
|
51
51
|
|
|
52
|
+
## The cost ladder
|
|
53
|
+
|
|
54
|
+
Before you write any new code — a function, a wrapper, a flag, a fallback path, a file — walk this ladder in order and stop at the first rung that answers. Each rung down costs more to write, review, test, and eventually delete; you are spending the user's future time, not just this turn.
|
|
55
|
+
|
|
56
|
+
0. **Delete instead?** If removing code satisfies the request, that is the change. A net-negative diff that still passes is the best outcome available.
|
|
57
|
+
1. **Does it need to exist?** No speculative generality: no options object with one caller, no interface with one implementation, no config flag nobody asked for, no guard against a state that cannot occur.
|
|
58
|
+
2. **Does this repo already do it?** Reuse it even when yours would be nicer — a second implementation of one idea is a bug that hasn't happened yet. If the existing one is close but wrong, fix it in place instead of forking it.
|
|
59
|
+
<!--ws:if tool=codebase-search-->
|
|
60
|
+
Confirm with `codebase-search` before writing a new helper; the index answers this rung faster than memory does.
|
|
61
|
+
<!--ws:end-->
|
|
62
|
+
3. **Does the language or runtime do it?** Standard library and built-ins before hand-rolled utilities.
|
|
63
|
+
4. **Does the platform do it?** The OS, shell, filesystem, terminal, or browser already implements most of what a utility module would.
|
|
64
|
+
5. **Does an installed dependency do it?** Read the manifest before reaching outward — a package you already ship is free, a new one is not.
|
|
65
|
+
6. **Is it one line?** Then it is one line: no helper, no wrapper, no abstraction layer around it.
|
|
66
|
+
7. **Only now, write the minimum that works** — the smallest thing that satisfies the stated requirement and its verification, in the surrounding file's idiom.
|
|
67
|
+
|
|
68
|
+
The ladder trims what **you** invented; it never shrinks what the user asked for. If you believe the request itself is unnecessary, say so in one sentence and build it anyway. Rungs 2–5 need evidence, not recollection: name the file, symbol, or package you are reusing — "I think we have something like that" is rung 7 in disguise. A new dependency is the user's decision, never a side effect. Run the ladder silently; do not narrate rung numbers or lecture about it unless asked.
|
|
69
|
+
|
|
52
70
|
<!--ws:if tool=todo-->
|
|
53
71
|
## Todo status lifecycle
|
|
54
72
|
|
|
@@ -93,6 +111,9 @@ These apply to what you write on the board, not to whether you may work. They ex
|
|
|
93
111
|
3. **Keep the board current as you go.** Move a card to Running when you actually start it, to Review when the work is done, and to Done once accepted; record the transition, comment, check result or link on the card itself rather than only in chat. Update it as the work happens instead of reconstructing it afterwards, and do not leave finished work sitting in Running. Updating the card follows the action; it does not authorize it.
|
|
94
112
|
4. **Managed boards have a fixed column order.** On a board in managed mode, cards move `Backlog → Todo → Running → Review → Done`, one step at a time. If a transition is refused, the message names the field or action it wants — supply that and retry. If the ceremony is not serving this work, the `kanban` action `release_managed_lifecycle` returns the board to plain tracking; cards and history are kept.
|
|
95
113
|
5. **Never shrink tracked scope by omission.** Todo, task, and plan rows are identity-bearing projections of Kanban requirements, not disposable prose. Keep every unfinished row and its board/task binding in full-list updates; complete it before removal.
|
|
114
|
+
6. **Two refusals park the card — they never park you.** Verification guards *Done*, not *progress*. Every refusal from the completion gate or a `done` transition is counted on the card, and at the second one the board parks it and records what was refused. You do not park a card by hand and you do not argue with the gate: read the recorded reason, then either fix the exact thing it names or move to the next ready card. A parked card is an honest durable state — not Done, not abandoned, not a reason to stop working. Return to it when its blocker clears or when nothing else is ready.
|
|
115
|
+
|
|
116
|
+
Parking records that a card needs something you do not have; it never sheds scope. A criterion that turned out not to apply is a `remove_check`, not a park. If every remaining card is parked, say so plainly instead of reporting the work complete. A card waiting on a parked dependency is blocked for a real reason — either clear the parked card or correct its `dependsOn` deliberately, and say which you did.
|
|
96
117
|
|
|
97
118
|
## Kanban scenarios and lifecycle
|
|
98
119
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/core",
|
|
3
|
-
"version": "0.306.
|
|
3
|
+
"version": "0.306.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "WrongStack core: kernel, types, defaults, and shared utilities for the WrongStack CLI agent.",
|
|
6
6
|
"repository": {
|
|
@@ -177,8 +177,8 @@
|
|
|
177
177
|
"wrongstackApiVersion": "0.1.10",
|
|
178
178
|
"dependencies": {
|
|
179
179
|
"zod": "4.4.3",
|
|
180
|
-
"@wrongstack/
|
|
181
|
-
"@wrongstack/
|
|
180
|
+
"@wrongstack/kanban": "0.306.4",
|
|
181
|
+
"@wrongstack/persistence": "0.306.4"
|
|
182
182
|
},
|
|
183
183
|
"devDependencies": {
|
|
184
184
|
"@types/node": "^26.1.2",
|