@brainervirus/workit-core 0.6.0 → 0.7.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 +10 -14
- package/scripts/doctor-check.ts +20 -0
- package/scripts/install-cursor-plugin.sh +51 -28
- package/scripts/install-opencode-plugin.sh +25 -22
- package/scripts/rewrite-workspace-deps.ts +15 -9
- package/scripts/sync-runtime.sh +71 -19
- package/scripts/vendor-assets.ts +37 -0
- package/skills/wk-implement/SKILL.md +2 -2
- package/skills/wk-pr/SKILL.md +1 -1
- package/src/core/boundary.ts +27 -0
- package/src/core/branch-policy.ts +63 -0
- package/src/core/branch.ts +166 -57
- package/src/core/changelog.ts +17 -14
- package/src/core/config-guard.ts +9 -2
- package/src/core/config.ts +227 -32
- package/src/core/detector.ts +22 -11
- package/src/core/docs-layout.ts +251 -0
- package/src/core/docs-migration.ts +639 -0
- package/src/core/docs-repo.ts +58 -21
- package/src/core/docs-validate.ts +181 -43
- package/src/core/doctor.ts +801 -0
- package/src/core/flow-state.ts +1579 -137
- package/src/core/git.ts +22 -5
- package/src/core/gitignore.ts +11 -2
- package/src/core/handoff-context.ts +18 -5
- package/src/{tools/handoff.ts → core/handoff-tools.ts} +16 -63
- package/src/core/hygiene.ts +53 -17
- package/src/core/init.ts +128 -31
- package/src/core/logger.ts +321 -0
- package/src/core/package-root.ts +28 -0
- package/src/core/parse-sections.ts +2 -2
- package/src/core/plan-tasks.ts +13 -3
- package/src/core/ports/init-toolkit-status.ts +1 -1
- package/src/core/ports/vcs-verify-token.ts +1 -1
- package/src/core/ports/youtrack-api.ts +4 -2
- package/src/core/ports/youtrack-config.ts +1 -3
- package/src/core/ports/youtrack-verify-token.ts +1 -1
- package/src/core/pr-create.ts +156 -29
- package/src/core/present.ts +11 -2
- package/src/core/registration.ts +215 -0
- package/src/core/reminder.ts +1 -2
- package/src/core/repo-context.ts +447 -0
- package/src/core/repo-tool.ts +4 -1
- package/src/core/repo-tools.ts +23 -0
- package/src/core/rules.ts +10 -7
- package/src/core/safe-write.ts +22 -0
- package/src/core/scripts.ts +3 -39
- package/src/core/sdd.ts +56 -31
- package/src/core/setup-state.ts +54 -0
- package/src/core/setup.ts +1216 -0
- package/src/core/skill-manifests.ts +95 -0
- package/src/core/support-matrix.ts +12 -0
- package/src/core/sync-runtime.ts +348 -0
- package/src/core/templates.ts +16 -6
- package/src/core/vcs-config.ts +191 -62
- package/src/core/verify-parse.ts +4 -2
- package/src/core/verify-project.ts +181 -0
- package/src/core/workspaces.ts +136 -17
- package/src/core/youtrack-tools.ts +228 -0
- package/src/core/youtrack.ts +320 -87
- package/src/core.ts +18 -3
- package/templates/execution-contract.md +9 -7
- package/templates/superpowers-doc-contract.md +5 -4
- package/scripts/_shared/common.sh +0 -158
- package/scripts/changelog-context.sh +0 -42
- package/scripts/docs-refresh-context.sh +0 -40
- package/scripts/init/apply.sh +0 -5
- package/scripts/init/status.sh +0 -5
- package/scripts/init/toolkit-status.sh +0 -5
- package/scripts/pr-create.sh +0 -5
- package/scripts/pr-ready-context.sh +0 -88
- package/scripts/present/ascii-wireframe.sh +0 -5
- package/scripts/present/flow-diagram.sh +0 -5
- package/scripts/release-notes-context.sh +0 -40
- package/scripts/vcs/config.sh +0 -5
- package/scripts/vcs/merged-style.sh +0 -5
- package/scripts/vcs/token-create-urls.sh +0 -5
- package/scripts/vcs/verify-token.sh +0 -5
- package/scripts/verify-project.sh +0 -140
- package/scripts/youtrack/api.sh +0 -5
- package/scripts/youtrack/config.sh +0 -5
- package/scripts/youtrack/greeting.sh +0 -5
- package/scripts/youtrack/parse-duration.sh +0 -5
- package/scripts/youtrack/token-create-url.sh +0 -5
- package/scripts/youtrack/verify-token.sh +0 -5
- package/scripts/youtrack/work-date-ms.sh +0 -5
- package/src/tools/docs-repo.ts +0 -42
- package/src/tools/flow.ts +0 -88
- package/src/tools/index.ts +0 -22
- package/src/tools/present.ts +0 -45
- package/src/tools/repo.ts +0 -357
- package/src/tools/rules.ts +0 -30
- package/src/tools/sdd.ts +0 -189
- package/src/tools/templates.ts +0 -27
- package/src/tools/youtrack.ts +0 -360
package/src/core/sdd.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
appendFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
statSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
2
9
|
import { execFileSync } from "node:child_process";
|
|
3
10
|
import path from "node:path";
|
|
4
11
|
import { parseTasksFromPlan } from "./docs-validate";
|
|
5
12
|
import { docsValidate } from "./docs-validate";
|
|
6
13
|
import { readFlowState } from "./flow-state";
|
|
14
|
+
import { resolveCanonicalLayout, resolveDocsPath } from "./docs-layout";
|
|
7
15
|
|
|
8
16
|
const posix = (p: string) => p.split(path.sep).join("/");
|
|
9
17
|
|
|
10
|
-
export const slugFromPlan = (planPath: string) => {
|
|
11
|
-
const dirName = path.basename(path.dirname(planPath));
|
|
12
|
-
return dirName === "." || dirName === "/" || dirName === "" ? "" : dirName;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
18
|
/** OpenCode todowrite payload. SDD ledger is persistence, not a UI substitute. */
|
|
16
19
|
export function todosFromTasks(
|
|
17
20
|
tasks: { id: number | string; title: string }[],
|
|
@@ -40,13 +43,12 @@ export function sddContext({
|
|
|
40
43
|
plan_path?: string;
|
|
41
44
|
workspace_root: string;
|
|
42
45
|
}) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (!
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
46
|
+
// One shared contained path contract (DC-01, DC-02): slug and/or plan_path
|
|
47
|
+
// resolve to a canonical layout or the call fails before anything is read.
|
|
48
|
+
const resolved = resolveCanonicalLayout({ workspace_root, slug, plan_path });
|
|
49
|
+
if (!resolved.ok) return { error: resolved.error };
|
|
50
|
+
const cwd = resolved.layout.workspace;
|
|
51
|
+
const resolvedSlug = resolved.layout.slug;
|
|
50
52
|
if (!resolvedSlug) return { error: "slug or plan_path required" };
|
|
51
53
|
|
|
52
54
|
const sdd_dir = path.posix.join("docs", resolvedSlug, "sdd");
|
|
@@ -55,9 +57,12 @@ export function sddContext({
|
|
|
55
57
|
|
|
56
58
|
let progress_lines: string[] = [];
|
|
57
59
|
let completed_task_ids: number[] = [];
|
|
58
|
-
const absProgress = path.join(
|
|
60
|
+
const absProgress = path.join(resolved.layout.sdd, "progress.md");
|
|
59
61
|
if (existsSync(absProgress)) {
|
|
60
|
-
progress_lines = readFileSync(absProgress, "utf8")
|
|
62
|
+
progress_lines = readFileSync(absProgress, "utf8")
|
|
63
|
+
.split("\n")
|
|
64
|
+
.map((ln) => ln.trim())
|
|
65
|
+
.filter(Boolean);
|
|
61
66
|
const pat = /^Task\s+(\d+):\s+complete\b/i;
|
|
62
67
|
completed_task_ids = progress_lines
|
|
63
68
|
.map((ln) => pat.exec(ln)?.[1])
|
|
@@ -66,9 +71,13 @@ export function sddContext({
|
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
let manifest: Record<string, unknown> = {};
|
|
69
|
-
const absManifest = path.join(
|
|
74
|
+
const absManifest = path.join(resolved.layout.sdd, "manifest.json");
|
|
70
75
|
if (existsSync(absManifest)) {
|
|
71
|
-
try {
|
|
76
|
+
try {
|
|
77
|
+
manifest = JSON.parse(readFileSync(absManifest, "utf8"));
|
|
78
|
+
} catch {
|
|
79
|
+
manifest = {};
|
|
80
|
+
}
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
const legacy_path = path.join(cwd, ".superpowers/sdd");
|
|
@@ -77,13 +86,17 @@ export function sddContext({
|
|
|
77
86
|
let todos: { id: string; content: string; status: string }[] = [];
|
|
78
87
|
let task_count = 0;
|
|
79
88
|
if (plan_path) {
|
|
80
|
-
const
|
|
81
|
-
const planText = readFileSync(absPlan, "utf8");
|
|
89
|
+
const planText = readFileSync(resolved.layout.plan, "utf8");
|
|
82
90
|
const specMatch = planText.match(/^\*\*Spec:\*\*\s*(?:`([^`]+)`|(\S+))/m);
|
|
83
91
|
const spec_path = specMatch?.[1] ?? specMatch?.[2] ?? "";
|
|
84
92
|
if (spec_path) {
|
|
85
|
-
const validated = docsValidate({
|
|
86
|
-
|
|
93
|
+
const validated = docsValidate({
|
|
94
|
+
spec_path,
|
|
95
|
+
plan_path: path.relative(cwd, resolved.layout.plan),
|
|
96
|
+
workspace_root: cwd,
|
|
97
|
+
});
|
|
98
|
+
if (validated.ok === false)
|
|
99
|
+
return { ok: false, errors: validated.errors, error: validated.error };
|
|
87
100
|
}
|
|
88
101
|
const tasks = parseTasksFromPlan(planText);
|
|
89
102
|
if (tasks.length > 0) {
|
|
@@ -128,12 +141,13 @@ export function sddTaskBrief({
|
|
|
128
141
|
section_text: string;
|
|
129
142
|
workspace_root: string;
|
|
130
143
|
}) {
|
|
131
|
-
const
|
|
132
|
-
|
|
144
|
+
const contained = resolveDocsPath({ workspace_root, path: sdd_dir });
|
|
145
|
+
if (!contained.ok) return { error: contained.error };
|
|
146
|
+
const dir = contained.path;
|
|
133
147
|
mkdirSync(dir, { recursive: true });
|
|
134
148
|
const out = path.join(dir, `task-${task_id}-brief.md`);
|
|
135
149
|
writeFileSync(out, `# Task ${task_id} brief\n\n${section_text}\n`, "utf8");
|
|
136
|
-
const rel = posix(path.relative(
|
|
150
|
+
const rel = posix(path.relative(contained.base, out));
|
|
137
151
|
return { brief_path: rel, task_id };
|
|
138
152
|
}
|
|
139
153
|
|
|
@@ -148,16 +162,23 @@ export function sddReviewPackage({
|
|
|
148
162
|
head_sha: string;
|
|
149
163
|
workspace_root: string;
|
|
150
164
|
}) {
|
|
151
|
-
const
|
|
152
|
-
|
|
165
|
+
const contained = resolveDocsPath({ workspace_root, path: sdd_dir });
|
|
166
|
+
if (!contained.ok) return { error: contained.error };
|
|
167
|
+
const dir = contained.path;
|
|
153
168
|
mkdirSync(dir, { recursive: true });
|
|
154
169
|
const base7 = base_sha.slice(0, 7);
|
|
155
170
|
const head7 = head_sha.slice(0, 7);
|
|
156
171
|
const diffPath = path.join(dir, `review-${base7}..${head7}.diff`);
|
|
157
172
|
try {
|
|
158
|
-
const diff = execFileSync("git", ["diff", base_sha, head_sha], {
|
|
173
|
+
const diff = execFileSync("git", ["diff", base_sha, head_sha], {
|
|
174
|
+
cwd: contained.base,
|
|
175
|
+
encoding: "utf8",
|
|
176
|
+
// AR-14: a failed diff (missing shas, non-repo fixture) must stay in the
|
|
177
|
+
// captured error, never in the suite output.
|
|
178
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
179
|
+
});
|
|
159
180
|
writeFileSync(diffPath, diff, "utf8");
|
|
160
|
-
const rel = posix(path.relative(
|
|
181
|
+
const rel = posix(path.relative(contained.base, diffPath));
|
|
161
182
|
return { diff_path: rel, base_sha, head_sha, base7, head7 };
|
|
162
183
|
} catch (error) {
|
|
163
184
|
return { error: error instanceof Error ? error.message : "git diff failed" };
|
|
@@ -175,14 +196,18 @@ export function sddAppendProgress({
|
|
|
175
196
|
line: string;
|
|
176
197
|
workspace_root: string;
|
|
177
198
|
}) {
|
|
178
|
-
const
|
|
179
|
-
|
|
199
|
+
const contained = resolveDocsPath({ workspace_root, path: progress_path });
|
|
200
|
+
if (!contained.ok) return { error: contained.error };
|
|
201
|
+
const path_ = contained.path;
|
|
180
202
|
const trimmed = line.trim();
|
|
181
203
|
if (!PROGRESS_RE.test(trimmed)) {
|
|
182
204
|
return { error: "invalid progress line format" };
|
|
183
205
|
}
|
|
206
|
+
if (existsSync(path_) && statSync(path_).isDirectory()) {
|
|
207
|
+
return { error: `progress path is a directory: ${progress_path}` };
|
|
208
|
+
}
|
|
184
209
|
mkdirSync(path.dirname(path_), { recursive: true });
|
|
185
210
|
appendFileSync(path_, trimmed + "\n", "utf8");
|
|
186
|
-
const rel = posix(path.relative(
|
|
211
|
+
const rel = posix(path.relative(contained.base, path_));
|
|
187
212
|
return { ok: true, line: trimmed, progress_path: rel };
|
|
188
213
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isConfigObject, resolveConfigDir } from "./config";
|
|
4
|
+
|
|
5
|
+
// WZ-06: typed setup-state reader distinguishing missing from malformed
|
|
6
|
+
// configuration. Purely read-only — nothing here ever writes; Apply decision
|
|
7
|
+
// (Task 13/14) reads this state and refuses to proceed on malformed files.
|
|
8
|
+
|
|
9
|
+
export type FileState = {
|
|
10
|
+
file: string;
|
|
11
|
+
status: "missing" | "valid" | "malformed";
|
|
12
|
+
error?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type SetupState = {
|
|
16
|
+
configDir: string;
|
|
17
|
+
config: FileState;
|
|
18
|
+
youtrack: FileState;
|
|
19
|
+
vcs: FileState;
|
|
20
|
+
workspaces: FileState;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const classifySetupFile = (dir: string, name: string): FileState => {
|
|
24
|
+
const file = path.join(dir, name);
|
|
25
|
+
let raw: string;
|
|
26
|
+
try {
|
|
27
|
+
raw = readFileSync(file, "utf8");
|
|
28
|
+
} catch {
|
|
29
|
+
// An existing-but-unreadable file (EACCES) is NOT "missing": mutations must
|
|
30
|
+
// never be generated against it. Report it as malformed so risky consumers
|
|
31
|
+
// (wizard/installer/doctor) block on the exact path.
|
|
32
|
+
if (existsSync(file)) {
|
|
33
|
+
return { file, status: "malformed", error: `${file} is not readable` };
|
|
34
|
+
}
|
|
35
|
+
return { file, status: "missing" };
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
if (!isConfigObject(parsed)) {
|
|
40
|
+
return { file, status: "malformed", error: `${file} is not a JSON object` };
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
return { file, status: "malformed", error: `${file} is not valid JSON` };
|
|
44
|
+
}
|
|
45
|
+
return { file, status: "valid" };
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const readSetupState = (dir: string = resolveConfigDir()): SetupState => ({
|
|
49
|
+
configDir: dir,
|
|
50
|
+
config: classifySetupFile(dir, "config.json"),
|
|
51
|
+
youtrack: classifySetupFile(dir, "youtrack.json"),
|
|
52
|
+
vcs: classifySetupFile(dir, "vcs.json"),
|
|
53
|
+
workspaces: classifySetupFile(dir, "workspaces.json"),
|
|
54
|
+
});
|