@brainervirus/workit-core 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +180 -0
- package/commands/wk-changelog.md +2 -0
- package/commands/wk-commit.md +2 -0
- package/commands/wk-docs-refresh.md +2 -0
- package/commands/wk-handoff.md +2 -0
- package/commands/wk-implement.md +2 -0
- package/commands/wk-init.md +2 -0
- package/commands/wk-issue-update.md +2 -0
- package/commands/wk-meetings.md +2 -0
- package/commands/wk-pr.md +2 -0
- package/commands/wk-release-notes.md +2 -0
- package/commands/wk-status.md +2 -0
- package/commands/wk-verify.md +2 -0
- package/package.json +43 -0
- package/scripts/_shared/common.sh +158 -0
- package/scripts/changelog-context.sh +42 -0
- package/scripts/docs-refresh-context.sh +40 -0
- package/scripts/fixtures/sample-plan.md +15 -0
- package/scripts/fixtures/sample-sdd/progress.md +2 -0
- package/scripts/init/apply.sh +5 -0
- package/scripts/init/status.sh +5 -0
- package/scripts/init/toolkit-status.sh +5 -0
- package/scripts/install-cursor-plugin.sh +83 -0
- package/scripts/install-opencode-plugin.sh +76 -0
- package/scripts/pr-create.sh +5 -0
- package/scripts/pr-ready-context.sh +88 -0
- package/scripts/present/ascii-wireframe.sh +5 -0
- package/scripts/present/flow-diagram.sh +5 -0
- package/scripts/release-notes-context.sh +40 -0
- package/scripts/rewrite-workspace-deps.ts +17 -0
- package/scripts/run-cursor-mcp.sh +10 -0
- package/scripts/sync-runtime.sh +96 -0
- package/scripts/update-superpowers.sh +56 -0
- package/scripts/vcs/config.sh +5 -0
- package/scripts/vcs/merged-style.sh +5 -0
- package/scripts/vcs/token-create-urls.sh +5 -0
- package/scripts/vcs/verify-token.sh +5 -0
- package/scripts/verify-project.sh +140 -0
- package/scripts/youtrack/api.sh +5 -0
- package/scripts/youtrack/config.sh +5 -0
- package/scripts/youtrack/greeting.sh +5 -0
- package/scripts/youtrack/parse-duration.sh +5 -0
- package/scripts/youtrack/token-create-url.sh +5 -0
- package/scripts/youtrack/verify-token.sh +5 -0
- package/scripts/youtrack/work-date-ms.sh +5 -0
- package/skills/wk-changelog/SKILL.md +15 -0
- package/skills/wk-commit/SKILL.md +16 -0
- package/skills/wk-docs-refresh/SKILL.md +15 -0
- package/skills/wk-handoff/SKILL.md +17 -0
- package/skills/wk-implement/SKILL.md +41 -0
- package/skills/wk-init/SKILL.md +31 -0
- package/skills/wk-issue-update/SKILL.md +27 -0
- package/skills/wk-issue-update/references/youtrack-update-style.md +81 -0
- package/skills/wk-meetings/SKILL.md +17 -0
- package/skills/wk-pr/SKILL.md +27 -0
- package/skills/wk-release-notes/SKILL.md +15 -0
- package/skills/wk-status/SKILL.md +16 -0
- package/skills/wk-verify/SKILL.md +16 -0
- package/src/core/branch.ts +246 -0
- package/src/core/changelog.ts +312 -0
- package/src/core/config-guard.ts +26 -0
- package/src/core/config.ts +73 -0
- package/src/core/detector.ts +207 -0
- package/src/core/doc-render.ts +14 -0
- package/src/core/docs-repo.ts +196 -0
- package/src/core/docs-validate.ts +255 -0
- package/src/core/flow-state.ts +225 -0
- package/src/core/git.ts +56 -0
- package/src/core/gitignore.ts +43 -0
- package/src/core/handoff-context.ts +115 -0
- package/src/core/hygiene.ts +77 -0
- package/src/core/init.ts +443 -0
- package/src/core/parse-sections.ts +24 -0
- package/src/core/plan-tasks.ts +33 -0
- package/src/core/ports/init-apply.ts +15 -0
- package/src/core/ports/init-status.ts +4 -0
- package/src/core/ports/init-toolkit-status.ts +4 -0
- package/src/core/ports/pr-create.ts +22 -0
- package/src/core/ports/present-ascii.ts +10 -0
- package/src/core/ports/present-flow.ts +10 -0
- package/src/core/ports/vcs-config.ts +14 -0
- package/src/core/ports/vcs-merged-style.ts +5 -0
- package/src/core/ports/vcs-token-create-urls.ts +4 -0
- package/src/core/ports/vcs-verify-token.ts +4 -0
- package/src/core/ports/youtrack-api.ts +17 -0
- package/src/core/ports/youtrack-config.ts +23 -0
- package/src/core/ports/youtrack-greeting.ts +10 -0
- package/src/core/ports/youtrack-parse-duration.ts +14 -0
- package/src/core/ports/youtrack-token-create-url.ts +4 -0
- package/src/core/ports/youtrack-verify-token.ts +12 -0
- package/src/core/ports/youtrack-work-date-ms.ts +10 -0
- package/src/core/pr-create.ts +212 -0
- package/src/core/present.ts +100 -0
- package/src/core/reminder.ts +82 -0
- package/src/core/repo-tool.ts +9 -0
- package/src/core/rules.ts +122 -0
- package/src/core/scripts.ts +42 -0
- package/src/core/sdd.ts +188 -0
- package/src/core/templates.ts +37 -0
- package/src/core/vcs-config.ts +252 -0
- package/src/core/verify-parse.ts +27 -0
- package/src/core/workspaces.ts +81 -0
- package/src/core/youtrack.ts +488 -0
- package/src/core.ts +62 -0
- package/src/state.ts +22 -0
- package/src/tools/docs-repo.ts +42 -0
- package/src/tools/flow.ts +88 -0
- package/src/tools/handoff.ts +160 -0
- package/src/tools/index.ts +22 -0
- package/src/tools/present.ts +45 -0
- package/src/tools/repo.ts +357 -0
- package/src/tools/rules.ts +30 -0
- package/src/tools/sdd.ts +189 -0
- package/src/tools/templates.ts +27 -0
- package/src/tools/youtrack.ts +356 -0
- package/templates/execution-contract.md +56 -0
- package/templates/greeting.md +1 -0
- package/templates/headers.md +3 -0
- package/templates/hygiene/.editorconfig +8 -0
- package/templates/hygiene/.gitattributes +3 -0
- package/templates/hygiene/CHANGELOG.md +14 -0
- package/templates/hygiene/CONTRIBUTING.md +3 -0
- package/templates/hygiene/LICENSE +21 -0
- package/templates/hygiene/README.md +3 -0
- package/templates/issue-update.md +6 -0
- package/templates/plan-template.md +25 -0
- package/templates/spec-template.md +51 -0
- package/templates/superpowers-doc-contract.md +69 -0
- package/vendor/superpowers/skills/brainstorming/SKILL.md +159 -0
- package/vendor/superpowers/skills/brainstorming/scripts/frame-template.html +213 -0
- package/vendor/superpowers/skills/brainstorming/scripts/helper.js +167 -0
- package/vendor/superpowers/skills/brainstorming/scripts/server.cjs +723 -0
- package/vendor/superpowers/skills/brainstorming/scripts/start-server.sh +209 -0
- package/vendor/superpowers/skills/brainstorming/scripts/stop-server.sh +120 -0
- package/vendor/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/vendor/superpowers/skills/brainstorming/visual-companion.md +291 -0
- package/vendor/superpowers/skills/dispatching-parallel-agents/SKILL.md +185 -0
- package/vendor/superpowers/skills/executing-plans/SKILL.md +70 -0
- package/vendor/superpowers/skills/finishing-a-development-branch/SKILL.md +241 -0
- package/vendor/superpowers/skills/receiving-code-review/SKILL.md +213 -0
- package/vendor/superpowers/skills/requesting-code-review/SKILL.md +103 -0
- package/vendor/superpowers/skills/requesting-code-review/code-reviewer.md +172 -0
- package/vendor/superpowers/skills/subagent-driven-development/SKILL.md +418 -0
- package/vendor/superpowers/skills/subagent-driven-development/implementer-prompt.md +139 -0
- package/vendor/superpowers/skills/subagent-driven-development/scripts/review-package +44 -0
- package/vendor/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +22 -0
- package/vendor/superpowers/skills/subagent-driven-development/scripts/task-brief +40 -0
- package/vendor/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +188 -0
- package/vendor/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/vendor/superpowers/skills/systematic-debugging/SKILL.md +296 -0
- package/vendor/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/vendor/superpowers/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/vendor/superpowers/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/vendor/superpowers/skills/systematic-debugging/find-polluter.sh +63 -0
- package/vendor/superpowers/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/vendor/superpowers/skills/systematic-debugging/test-academic.md +14 -0
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/vendor/superpowers/skills/test-driven-development/SKILL.md +371 -0
- package/vendor/superpowers/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/vendor/superpowers/skills/using-git-worktrees/SKILL.md +202 -0
- package/vendor/superpowers/skills/using-superpowers/SKILL.md +62 -0
- package/vendor/superpowers/skills/using-superpowers/references/antigravity-tools.md +23 -0
- package/vendor/superpowers/skills/using-superpowers/references/codex-tools.md +39 -0
- package/vendor/superpowers/skills/using-superpowers/references/pi-tools.md +16 -0
- package/vendor/superpowers/skills/verification-before-completion/SKILL.md +139 -0
- package/vendor/superpowers/skills/writing-plans/SKILL.md +174 -0
- package/vendor/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/vendor/superpowers/skills/writing-skills/SKILL.md +689 -0
- package/vendor/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/vendor/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/vendor/superpowers/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/vendor/superpowers/skills/writing-skills/persuasion-principles.md +187 -0
- package/vendor/superpowers/skills/writing-skills/render-graphs.js +168 -0
- package/vendor/superpowers/skills/writing-skills/testing-skills-with-subagents.md +384 -0
package/src/core/init.ts
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { configDir } from "./config";
|
|
4
|
+
import { PLUGIN_ROOT } from "./scripts";
|
|
5
|
+
import { resolveWorkspace, workspacesPath } from "./workspaces";
|
|
6
|
+
import { vcsTokenCreateUrls, vcsVerifyToken } from "./vcs-config";
|
|
7
|
+
import { youTrackTokenCreateUrl, youTrackVerifyToken } from "./youtrack";
|
|
8
|
+
|
|
9
|
+
const TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
10
|
+
|
|
11
|
+
const readJson = (p: string): Record<string, any> | null => {
|
|
12
|
+
try { return JSON.parse(fs.readFileSync(p, "utf8")) as Record<string, any>; } catch { return null; }
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const isPlaceholder = (text: string): boolean =>
|
|
16
|
+
text === TOKEN_PLACEHOLDER || text.startsWith(TOKEN_PLACEHOLDER);
|
|
17
|
+
|
|
18
|
+
const modeOk = (p: string): boolean =>
|
|
19
|
+
process.platform === "win32" || (fs.statSync(p).mode & 0o777) === 0o600;
|
|
20
|
+
|
|
21
|
+
const resolvePath = (p: string): string => {
|
|
22
|
+
try { return fs.realpathSync(p); } catch { return path.resolve(p); }
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Port of scripts/init/status.sh — filesystem init state. */
|
|
26
|
+
export function initStatusData(configDirPath = configDir()): Record<string, any> {
|
|
27
|
+
const ytJson = path.join(configDirPath, "youtrack.json");
|
|
28
|
+
const vcsJson = path.join(configDirPath, "vcs.json");
|
|
29
|
+
const items: Array<Record<string, any>> = [];
|
|
30
|
+
|
|
31
|
+
let youtrackConfig: Record<string, any> | null = null;
|
|
32
|
+
let youtrackTokenCreate: Record<string, any> | null = null;
|
|
33
|
+
const ytParsed = readJson(ytJson);
|
|
34
|
+
if (ytParsed) {
|
|
35
|
+
const base = String(ytParsed.baseUrl ?? "").replace(/\/+$/, "");
|
|
36
|
+
const meeting = String(ytParsed.meetingIssue ?? "");
|
|
37
|
+
const tokenFile = String(ytParsed.tokenFile ?? "");
|
|
38
|
+
const meetingIssues = [];
|
|
39
|
+
const rawMeetings = (ytParsed.meetingIssues ?? {}) as Record<string, any>;
|
|
40
|
+
if (rawMeetings && typeof rawMeetings === "object") {
|
|
41
|
+
for (const [key, item] of Object.entries(rawMeetings)) {
|
|
42
|
+
const iss = String(item?.issue ?? "");
|
|
43
|
+
meetingIssues.push({
|
|
44
|
+
key,
|
|
45
|
+
issue: iss,
|
|
46
|
+
label: item?.label ?? iss,
|
|
47
|
+
workItemText: item?.workItemText ?? "Reuniones",
|
|
48
|
+
url: item?.url ?? (base && iss ? `${base}/issue/${iss}` : null),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const expanded = tokenFile ? path.resolve(tokenFile) : null;
|
|
53
|
+
const resolvedTokenFile = expanded && fs.existsSync(expanded)
|
|
54
|
+
? resolvePath(expanded)
|
|
55
|
+
: expanded
|
|
56
|
+
? (path.isAbsolute(expanded) ? expanded : path.resolve(configDirPath, expanded))
|
|
57
|
+
: null;
|
|
58
|
+
youtrackConfig = {
|
|
59
|
+
config_edit_path: resolvePath(ytJson),
|
|
60
|
+
baseUrl: base,
|
|
61
|
+
meetingIssue: meeting,
|
|
62
|
+
meetingIssues,
|
|
63
|
+
meetingIssueUrl: base && meeting ? `${base}/issue/${meeting}` : null,
|
|
64
|
+
defaultMention: ytParsed.defaultMention,
|
|
65
|
+
timezone: ytParsed.timezone,
|
|
66
|
+
locale: ytParsed.locale,
|
|
67
|
+
tokenFile: resolvedTokenFile,
|
|
68
|
+
tokenDefaults: ytParsed.tokenDefaults,
|
|
69
|
+
timeLogging: {
|
|
70
|
+
meetings: { options: meetingIssues, skill: "/wk-meetings", logsTime: true, postsComment: false },
|
|
71
|
+
taskWork: { issueSource: "active spec/plan **YouTrack:** field or --issue", skill: "/wk-issue-update", logsTime: true, postsComment: true },
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
youtrackTokenCreate = youTrackTokenCreateUrl().data;
|
|
75
|
+
if (youtrackTokenCreate && !(youtrackConfig as Record<string, any>).error) {
|
|
76
|
+
youtrackConfig.tokenCreate = youtrackTokenCreate;
|
|
77
|
+
}
|
|
78
|
+
} else if (fs.existsSync(ytJson)) {
|
|
79
|
+
youtrackConfig = { config_edit_path: resolvePath(ytJson), error: "invalid youtrack.json" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
items.push({
|
|
83
|
+
id: "youtrack_json",
|
|
84
|
+
label: "YouTrack config",
|
|
85
|
+
ok: fs.existsSync(ytJson) && youtrackConfig !== null && !("error" in youtrackConfig),
|
|
86
|
+
path: fs.existsSync(ytJson) ? resolvePath(ytJson) : path.resolve(ytJson),
|
|
87
|
+
config_edit_path: resolvePath(ytJson),
|
|
88
|
+
fix: "workflow_toolkit_init_apply action=youtrack_scaffold",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const ytTokenPath = youtrackConfig?.tokenFile ?? path.join(configDirPath, "youtrack.token");
|
|
92
|
+
const tokenText = fs.existsSync(ytTokenPath) ? fs.readFileSync(ytTokenPath, "utf8").trim() : "";
|
|
93
|
+
const placeholder = fs.existsSync(ytTokenPath) && isPlaceholder(tokenText);
|
|
94
|
+
const tokenOk = fs.existsSync(ytTokenPath) && modeOk(ytTokenPath) && Boolean(tokenText) && !isPlaceholder(tokenText);
|
|
95
|
+
|
|
96
|
+
const youtrackTokenItem: Record<string, any> = {
|
|
97
|
+
id: "youtrack_token",
|
|
98
|
+
label: "YouTrack API token (mode 600, not placeholder)",
|
|
99
|
+
ok: tokenOk,
|
|
100
|
+
path: fs.existsSync(ytTokenPath) ? resolvePath(ytTokenPath) : path.resolve(ytTokenPath),
|
|
101
|
+
token_edit_path: fs.existsSync(ytTokenPath) ? resolvePath(ytTokenPath) : path.resolve(ytTokenPath),
|
|
102
|
+
placeholder,
|
|
103
|
+
fix: `Open ${resolvePath(ytTokenPath)} — replace ${TOKEN_PLACEHOLDER} with your permanent token, save, then /wk-status`,
|
|
104
|
+
};
|
|
105
|
+
if (youtrackTokenCreate) {
|
|
106
|
+
if (youtrackTokenCreate.createUrl) youtrackTokenItem.token_create_url = youtrackTokenCreate.createUrl;
|
|
107
|
+
if (youtrackTokenCreate.docsUrl) youtrackTokenItem.token_create_docs_url = youtrackTokenCreate.docsUrl;
|
|
108
|
+
if (youtrackTokenCreate.scopes) youtrackTokenItem.token_scopes = youtrackTokenCreate.scopes;
|
|
109
|
+
if (youtrackTokenCreate.tokenName) youtrackTokenItem.token_name = youtrackTokenCreate.tokenName;
|
|
110
|
+
if (youtrackTokenCreate.steps) youtrackTokenItem.token_create_steps = youtrackTokenCreate.steps;
|
|
111
|
+
youtrackTokenItem.token_prefill_supported = youtrackTokenCreate.prefillSupported ?? false;
|
|
112
|
+
}
|
|
113
|
+
items.push(youtrackTokenItem);
|
|
114
|
+
|
|
115
|
+
let vcsCfg: Record<string, any> | null = null;
|
|
116
|
+
let tokenCreateUrls: Record<string, any> | null = null;
|
|
117
|
+
const vcsParsed = readJson(vcsJson);
|
|
118
|
+
if (vcsParsed) {
|
|
119
|
+
const provider = String(vcsParsed.provider ?? "gitlab").toLowerCase();
|
|
120
|
+
const tokenFiles: Record<string, string> = {};
|
|
121
|
+
for (const k of ["gitlab", "github"]) {
|
|
122
|
+
tokenFiles[k] = String(vcsParsed[k]?.tokenFile ?? path.join(configDirPath, `${k}.token`));
|
|
123
|
+
}
|
|
124
|
+
vcsCfg = {
|
|
125
|
+
config_edit_path: resolvePath(vcsJson),
|
|
126
|
+
provider,
|
|
127
|
+
defaultTargetBranch: vcsParsed.defaultTargetBranch ?? "develop",
|
|
128
|
+
pr: vcsParsed.pr ?? {},
|
|
129
|
+
tokenDefaults: vcsParsed.tokenDefaults,
|
|
130
|
+
gitlab: vcsParsed.gitlab,
|
|
131
|
+
github: vcsParsed.github,
|
|
132
|
+
skill: "/wk-pr",
|
|
133
|
+
switchHint: 'Set "provider" to "gitlab" or "github" in vcs.json',
|
|
134
|
+
};
|
|
135
|
+
tokenCreateUrls = vcsTokenCreateUrls();
|
|
136
|
+
if (!("error" in vcsCfg)) {
|
|
137
|
+
vcsCfg.tokenCreate = tokenCreateUrls.active;
|
|
138
|
+
vcsCfg.tokenCreateUrls = { gitlab: tokenCreateUrls.gitlab, github: tokenCreateUrls.github };
|
|
139
|
+
}
|
|
140
|
+
} else if (fs.existsSync(vcsJson)) {
|
|
141
|
+
vcsCfg = { config_edit_path: resolvePath(vcsJson), error: "invalid vcs.json" };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
items.push({
|
|
145
|
+
id: "vcs_json",
|
|
146
|
+
label: "VCS config (GitLab / GitHub)",
|
|
147
|
+
ok: fs.existsSync(vcsJson) && vcsCfg !== null && !("error" in vcsCfg),
|
|
148
|
+
path: fs.existsSync(vcsJson) ? resolvePath(vcsJson) : path.resolve(vcsJson),
|
|
149
|
+
config_edit_path: resolvePath(vcsJson),
|
|
150
|
+
fix: "workflow_toolkit_init_apply action=vcs_scaffold",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const provActive = vcsCfg && !("error" in vcsCfg) ? vcsCfg.provider : null;
|
|
154
|
+
const tokenItem = (tid: string, label: string, rawPath: string, providerKey: string) => {
|
|
155
|
+
const t = path.isAbsolute(rawPath) ? rawPath : path.resolve(configDirPath, rawPath);
|
|
156
|
+
const abs = fs.existsSync(t) ? resolvePath(t) : path.resolve(t);
|
|
157
|
+
const text = fs.existsSync(t) ? fs.readFileSync(t, "utf8").trim() : "";
|
|
158
|
+
const ph = isPlaceholder(text);
|
|
159
|
+
const ok = fs.existsSync(t) && modeOk(t) && Boolean(text) && !ph;
|
|
160
|
+
const item: Record<string, any> = {
|
|
161
|
+
id: tid,
|
|
162
|
+
label,
|
|
163
|
+
ok,
|
|
164
|
+
path: abs,
|
|
165
|
+
token_edit_path: abs,
|
|
166
|
+
placeholder: ph,
|
|
167
|
+
fix: `Open ${abs} — replace ${TOKEN_PLACEHOLDER}, save, then /wk-status`,
|
|
168
|
+
required: provActive === providerKey,
|
|
169
|
+
};
|
|
170
|
+
if (tokenCreateUrls) {
|
|
171
|
+
const block = tokenCreateUrls[providerKey] ?? {};
|
|
172
|
+
if (block.createUrl) item.token_create_url = block.createUrl;
|
|
173
|
+
if (block.createUrlClassic) item.token_create_url_classic = block.createUrlClassic;
|
|
174
|
+
if (block.scopes) item.token_scopes = block.scopes;
|
|
175
|
+
if (block.permissions) item.token_permissions = block.permissions;
|
|
176
|
+
if (block.name) item.token_name = block.name;
|
|
177
|
+
}
|
|
178
|
+
return item;
|
|
179
|
+
};
|
|
180
|
+
const vcsTokenFiles: Record<string, string> = {};
|
|
181
|
+
for (const k of ["gitlab", "github"]) {
|
|
182
|
+
vcsTokenFiles[k] = String(vcsParsed?.[k]?.tokenFile ?? path.join(configDirPath, `${k}.token`));
|
|
183
|
+
}
|
|
184
|
+
items.push(tokenItem("gitlab_token", "GitLab token (mode 600, not placeholder)", vcsTokenFiles.gitlab, "gitlab"));
|
|
185
|
+
items.push(tokenItem("github_token", "GitHub token (mode 600, not placeholder)", vcsTokenFiles.github, "github"));
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
config_dir: configDirPath,
|
|
189
|
+
plugin_root: PLUGIN_ROOT,
|
|
190
|
+
token_placeholder: TOKEN_PLACEHOLDER,
|
|
191
|
+
youtrack_config: youtrackConfig,
|
|
192
|
+
vcs_config: vcsCfg,
|
|
193
|
+
items,
|
|
194
|
+
ready: false,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Port of scripts/init/toolkit-status.sh — filesystem + API health check. */
|
|
199
|
+
export function toolkitStatusData(configDirPath = configDir()): Record<string, any> {
|
|
200
|
+
const status = initStatusData(configDirPath);
|
|
201
|
+
const tokenItem = status.items.find((i: Record<string, any>) => i.id === "youtrack_token") ?? {};
|
|
202
|
+
const placeholder = Boolean(tokenItem.placeholder);
|
|
203
|
+
|
|
204
|
+
const vcsCfg = (status.vcs_config ?? {}) as Record<string, any>;
|
|
205
|
+
const provider = String(vcsCfg.provider ?? "gitlab").toLowerCase();
|
|
206
|
+
const vcsTokenId = provider === "gitlab" ? "gitlab_token" : "github_token";
|
|
207
|
+
const vcsTokenItem = status.items.find((i: Record<string, any>) => i.id === vcsTokenId) ?? {};
|
|
208
|
+
const vcsPlaceholder = vcsTokenItem.placeholder ?? true;
|
|
209
|
+
const vcsJsonOk = Boolean(status.items.find((i: Record<string, any>) => i.id === "vcs_json")?.ok);
|
|
210
|
+
|
|
211
|
+
const verify = placeholder
|
|
212
|
+
? { ok: false, error: "token still placeholder YOUR_TOKEN_HERE" }
|
|
213
|
+
: youTrackVerifyToken();
|
|
214
|
+
const vcsVerify = vcsPlaceholder
|
|
215
|
+
? { ok: false, error: "vcs token still placeholder YOUR_TOKEN_HERE" }
|
|
216
|
+
: vcsVerifyToken();
|
|
217
|
+
|
|
218
|
+
status.youtrack_verify = verify;
|
|
219
|
+
status.youtrack_ok = placeholder ? false : Boolean((verify as Record<string, any>).ok);
|
|
220
|
+
status.vcs_verify = vcsVerify;
|
|
221
|
+
status.vcs_ok = vcsJsonOk && !vcsPlaceholder ? Boolean((vcsVerify as Record<string, any>).ok) : false;
|
|
222
|
+
|
|
223
|
+
const fsReady = status.items.every((i: Record<string, any>) => i.required === false || i.ok);
|
|
224
|
+
status.ready = fsReady && Boolean(status.youtrack_ok) && (!vcsJsonOk || Boolean(status.vcs_ok));
|
|
225
|
+
|
|
226
|
+
if (placeholder) {
|
|
227
|
+
const tokenPath = tokenItem.token_edit_path ?? tokenItem.path ?? "";
|
|
228
|
+
const createUrl = tokenItem.token_create_url ?? status.youtrack_config?.tokenCreate?.createUrl;
|
|
229
|
+
status.token_edit_path = tokenPath;
|
|
230
|
+
if (createUrl) status.token_create_url = createUrl;
|
|
231
|
+
status.next_step = createUrl
|
|
232
|
+
? "Open the YouTrack create-token URL, New token (name workit, scope YouTrack), paste into the token file, save, then run /wk-status"
|
|
233
|
+
: "Open the YouTrack token file, replace YOUR_TOKEN_HERE, save, then run /wk-status";
|
|
234
|
+
} else if (vcsJsonOk && vcsPlaceholder) {
|
|
235
|
+
status.vcs_token_edit_path = vcsTokenItem.token_edit_path ?? "";
|
|
236
|
+
status.next_step = `Open ${vcsTokenItem.token_edit_path ?? ""}, paste your ${provider} token, save, then run /wk-status`;
|
|
237
|
+
} else if (!status.youtrack_ok) {
|
|
238
|
+
status.next_step = "Fix YouTrack token or re-run /wk-init";
|
|
239
|
+
} else if (vcsJsonOk && !status.vcs_ok) {
|
|
240
|
+
status.next_step = `Fix ${provider} token in vcs config or re-run /wk-init`;
|
|
241
|
+
} else if (status.ready) {
|
|
242
|
+
status.next_step = "All checks passed";
|
|
243
|
+
}
|
|
244
|
+
return status;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Port of scripts/init/apply.sh write_youtrack_json — env overrides honored.
|
|
248
|
+
const youtrackJsonContent = (dir: string): Record<string, any> => ({
|
|
249
|
+
baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://enghouseamg.youtrack.cloud",
|
|
250
|
+
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path.join(dir, "youtrack.token"),
|
|
251
|
+
timezone: process.env.WORKFLOW_YT_TIMEZONE ?? "America/Santiago",
|
|
252
|
+
locale: "es-CL",
|
|
253
|
+
defaultMention: process.env.WORKFLOW_YT_MENTION ?? "Alejandra.Flores",
|
|
254
|
+
greetings: { morning: "buenos días", afternoon: "buenas tardes" },
|
|
255
|
+
greetingCutoff: "12:00",
|
|
256
|
+
meetingIssue: process.env.WORKFLOW_YT_MEETING_ISSUE ?? "IRPT-12",
|
|
257
|
+
meetingIssues: {
|
|
258
|
+
general: {
|
|
259
|
+
issue: process.env.WORKFLOW_YT_MEETING_ISSUE ?? "IRPT-12",
|
|
260
|
+
label: "General meetings (Reuniones internas Team IRP)",
|
|
261
|
+
workItemText: "Reuniones",
|
|
262
|
+
},
|
|
263
|
+
web: {
|
|
264
|
+
issue: process.env.WORKFLOW_YT_WEB_MEETING_ISSUE ?? "NSXFT-21",
|
|
265
|
+
label: "Web meetings",
|
|
266
|
+
workItemText: "Reuniones web",
|
|
267
|
+
url: "https://enghouseamg.youtrack.cloud/projects/NSXFT/issues/NSXFT-21",
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
commentHeader: "# Actualización",
|
|
271
|
+
attachmentsHeaderImages: "## Adjunto capturas",
|
|
272
|
+
attachmentsHeaderFiles: "## Archivos adjuntos",
|
|
273
|
+
attachmentsHeaderMixed: "## Adjuntos",
|
|
274
|
+
tokenDefaults: {
|
|
275
|
+
name: "workit",
|
|
276
|
+
description: "OpenCode workit — /wk-issue-update and /wk-meetings",
|
|
277
|
+
scopes: ["YouTrack"],
|
|
278
|
+
profileTab: "account-security",
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const vcsJsonContent = (dir: string): Record<string, any> => ({
|
|
283
|
+
provider: process.env.WORKFLOW_VCS_PROVIDER ?? "gitlab",
|
|
284
|
+
defaultTargetBranch: process.env.WORKFLOW_VCS_TARGET_BRANCH ?? "develop",
|
|
285
|
+
gitlab: {
|
|
286
|
+
host: process.env.WORKFLOW_GITLAB_HOST ?? "gitlab.com",
|
|
287
|
+
apiUrl: process.env.WORKFLOW_GITLAB_API_URL ?? "https://gitlab.com/api/v4",
|
|
288
|
+
tokenFile: path.join(dir, "gitlab.token"),
|
|
289
|
+
},
|
|
290
|
+
github: {
|
|
291
|
+
host: process.env.WORKFLOW_GITHUB_HOST ?? "github.com",
|
|
292
|
+
tokenFile: path.join(dir, "github.token"),
|
|
293
|
+
},
|
|
294
|
+
pr: { squashOnMerge: true, removeSourceBranch: true, pushBranch: true, confirmSkip: true },
|
|
295
|
+
tokenDefaults: {
|
|
296
|
+
name: "workit",
|
|
297
|
+
description: "OpenCode workit — /wk-pr and glab/gh",
|
|
298
|
+
gitlabScopes: ["api"],
|
|
299
|
+
githubPermissions: { pull_requests: "write", contents: "write", metadata: "read" },
|
|
300
|
+
githubClassicScopes: ["repo"],
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
/** Port of scripts/init/apply.sh — confirmed scaffold actions. */
|
|
305
|
+
export function initApplyData(action: string, env: NodeJS.ProcessEnv = process.env): Record<string, any> {
|
|
306
|
+
const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
307
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
308
|
+
|
|
309
|
+
switch (action) {
|
|
310
|
+
case "youtrack_json": {
|
|
311
|
+
const out = path.join(dir, "youtrack.json");
|
|
312
|
+
fs.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + "\n", "utf8");
|
|
313
|
+
return { action, ok: true, path: out };
|
|
314
|
+
}
|
|
315
|
+
case "youtrack_token_placeholder": {
|
|
316
|
+
const p = path.join(dir, "youtrack.token");
|
|
317
|
+
fs.writeFileSync(p, TOKEN_PLACEHOLDER + "\n", { encoding: "utf8", mode: 0o600 });
|
|
318
|
+
const abs = path.resolve(p);
|
|
319
|
+
return {
|
|
320
|
+
action,
|
|
321
|
+
ok: true,
|
|
322
|
+
path: abs,
|
|
323
|
+
token_edit_path: abs,
|
|
324
|
+
placeholder: TOKEN_PLACEHOLDER,
|
|
325
|
+
instruction: `Open ${abs} in your editor, replace YOUR_TOKEN_HERE with your YouTrack permanent token, save, then run /wk-status`,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
case "youtrack_scaffold": {
|
|
329
|
+
const jsonOut = path.join(dir, "youtrack.json");
|
|
330
|
+
const tokenOut = path.join(dir, "youtrack.token");
|
|
331
|
+
fs.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + "\n", "utf8");
|
|
332
|
+
fs.writeFileSync(tokenOut, TOKEN_PLACEHOLDER + "\n", { encoding: "utf8", mode: 0o600 });
|
|
333
|
+
const configPath = path.resolve(jsonOut);
|
|
334
|
+
const tokenPath = path.resolve(tokenOut);
|
|
335
|
+
const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
336
|
+
process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
|
|
337
|
+
let tokenCreate: Record<string, any> = {};
|
|
338
|
+
try {
|
|
339
|
+
const cfg = readJson(configPath) ?? {};
|
|
340
|
+
const base = String(cfg.baseUrl ?? "").replace(/\/+$/, "");
|
|
341
|
+
const meeting = String(cfg.meetingIssue ?? "");
|
|
342
|
+
tokenCreate = youTrackTokenCreateUrl().data;
|
|
343
|
+
return {
|
|
344
|
+
action,
|
|
345
|
+
ok: true,
|
|
346
|
+
youtrack_json: configPath,
|
|
347
|
+
youtrack_token: tokenPath,
|
|
348
|
+
token_edit_path: tokenPath,
|
|
349
|
+
token_create_url: tokenCreate.createUrl,
|
|
350
|
+
token_create: tokenCreate,
|
|
351
|
+
config_edit_path: configPath,
|
|
352
|
+
placeholder: TOKEN_PLACEHOLDER,
|
|
353
|
+
youtrack_config: {
|
|
354
|
+
config_edit_path: configPath,
|
|
355
|
+
baseUrl: base,
|
|
356
|
+
meetingIssue: meeting,
|
|
357
|
+
meetingIssueUrl: base && meeting ? `${base}/issue/${meeting}` : null,
|
|
358
|
+
defaultMention: cfg.defaultMention,
|
|
359
|
+
timezone: cfg.timezone,
|
|
360
|
+
locale: cfg.locale,
|
|
361
|
+
tokenCreate,
|
|
362
|
+
timeLogging: {
|
|
363
|
+
meetings: { issue: meeting, skill: "/wk-meetings", logsTime: true, postsComment: false },
|
|
364
|
+
taskWork: {
|
|
365
|
+
issueSource: "active spec/plan **YouTrack:** field or --issue",
|
|
366
|
+
skill: "/wk-issue-update",
|
|
367
|
+
logsTime: true,
|
|
368
|
+
postsComment: true,
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
instruction: `Open the create-token URL, New token → name workit, scope YouTrack, paste into ${tokenPath}, then /wk-status.`,
|
|
373
|
+
};
|
|
374
|
+
} finally {
|
|
375
|
+
if (prev === undefined) delete process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
376
|
+
else process.env.WORKFLOW_YOUTRACK_CONFIG = prev;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
case "vcs_scaffold": {
|
|
380
|
+
const jsonOut = path.join(dir, "vcs.json");
|
|
381
|
+
fs.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(dir), null, 2) + "\n", "utf8");
|
|
382
|
+
const glPath = path.join(dir, "gitlab.token");
|
|
383
|
+
const ghPath = path.join(dir, "github.token");
|
|
384
|
+
for (const p of [glPath, ghPath]) {
|
|
385
|
+
fs.writeFileSync(p, TOKEN_PLACEHOLDER + "\n", { encoding: "utf8", mode: 0o600 });
|
|
386
|
+
}
|
|
387
|
+
const configPath = path.resolve(jsonOut);
|
|
388
|
+
const prev = process.env.WORKFLOW_VCS_CONFIG;
|
|
389
|
+
process.env.WORKFLOW_VCS_CONFIG = configPath;
|
|
390
|
+
try {
|
|
391
|
+
const cfg = readJson(configPath) ?? {};
|
|
392
|
+
const provider = String(cfg.provider ?? "gitlab");
|
|
393
|
+
const tokenUrls = vcsTokenCreateUrls();
|
|
394
|
+
const active = tokenUrls.active ?? {};
|
|
395
|
+
const activePath = provider === "gitlab" ? path.resolve(glPath) : path.resolve(ghPath);
|
|
396
|
+
return {
|
|
397
|
+
action,
|
|
398
|
+
ok: true,
|
|
399
|
+
vcs_json: configPath,
|
|
400
|
+
config_edit_path: configPath,
|
|
401
|
+
gitlab_token: path.resolve(glPath),
|
|
402
|
+
github_token: path.resolve(ghPath),
|
|
403
|
+
token_edit_path: activePath,
|
|
404
|
+
token_create_url: active.createUrl,
|
|
405
|
+
token_create_urls: tokenUrls,
|
|
406
|
+
vcs_config: {
|
|
407
|
+
config_edit_path: configPath,
|
|
408
|
+
provider,
|
|
409
|
+
defaultTargetBranch: cfg.defaultTargetBranch,
|
|
410
|
+
pr: cfg.pr,
|
|
411
|
+
tokenCreate: active,
|
|
412
|
+
tokenCreateUrls: { gitlab: tokenUrls.gitlab, github: tokenUrls.github },
|
|
413
|
+
switchHint: 'Change "provider" to "github" when you migrate — token files are separate',
|
|
414
|
+
skill: "/wk-pr",
|
|
415
|
+
},
|
|
416
|
+
instruction: `Open the create-token URL for ${provider}, click Create, paste into ${activePath}, then /wk-status.`,
|
|
417
|
+
};
|
|
418
|
+
} finally {
|
|
419
|
+
if (prev === undefined) delete process.env.WORKFLOW_VCS_CONFIG;
|
|
420
|
+
else process.env.WORKFLOW_VCS_CONFIG = prev;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
default: {
|
|
424
|
+
return { error: `unknown action ${action} (youtrack_scaffold|youtrack_json|youtrack_token_placeholder|vcs_scaffold)` };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function initStatus(): Record<string, any> {
|
|
430
|
+
const data = initStatusData();
|
|
431
|
+
return { ...data, workspaces: { resolved: resolveWorkspace(process.cwd()), path: workspacesPath() } };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export function toolkitStatus(): Record<string, any> {
|
|
435
|
+
return toolkitStatusData();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function initApply({ action, confirmed, env }: { action: string; confirmed: boolean; env?: Record<string, string> }): Record<string, any> {
|
|
439
|
+
if (!confirmed) return { error: "confirmed: true required" };
|
|
440
|
+
return { data: initApplyData(action, env ? { ...process.env, ...env } : process.env) };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export { TOKEN_PLACEHOLDER };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Split script stdout on ## Section headers (print_section output). */
|
|
2
|
+
export function parseSections(stdout: string): Record<string, string> {
|
|
3
|
+
const sections: Record<string, string> = {};
|
|
4
|
+
const parts = stdout.split(/\n## /);
|
|
5
|
+
for (const part of parts.slice(1)) {
|
|
6
|
+
const nl = part.indexOf('\n');
|
|
7
|
+
const title = part.slice(0, nl).trim();
|
|
8
|
+
sections[title] = part.slice(nl + 1).trim();
|
|
9
|
+
}
|
|
10
|
+
return sections;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parseKeyValueLines(text: string, keys: string[]): Record<string, string> {
|
|
14
|
+
const out: Record<string, string> = {};
|
|
15
|
+
for (const line of text.split('\n')) {
|
|
16
|
+
for (const key of keys) {
|
|
17
|
+
const prefix = `${key}: `;
|
|
18
|
+
if (line.startsWith(prefix)) {
|
|
19
|
+
out[key] = line.slice(prefix.length).trim();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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 { return readFileSync(p, "utf8"); } catch { return null; }
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function parsePlanTasks(
|
|
11
|
+
planPath: string,
|
|
12
|
+
workspaceRoot: string,
|
|
13
|
+
): { task_count: number; tasks: { id: number; title: string; section_text: string }[] } | { error: string } {
|
|
14
|
+
const cwd = path.resolve(workspaceRoot);
|
|
15
|
+
const resolved = path.isAbsolute(planPath) ? planPath : path.join(cwd, planPath);
|
|
16
|
+
const text = readSafe(resolved);
|
|
17
|
+
if (text === null) return { error: `plan file not found: ${planPath}` };
|
|
18
|
+
const tasks = parseTasksFromPlan(text);
|
|
19
|
+
if (tasks.length === 0) {
|
|
20
|
+
return { error: "no ### Task N sections found — plan must follow writing-plans format" };
|
|
21
|
+
}
|
|
22
|
+
return { task_count: tasks.length, tasks };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveHandoffBranch(
|
|
26
|
+
specPath: string,
|
|
27
|
+
planPath: string,
|
|
28
|
+
workspaceRoot: string,
|
|
29
|
+
): { branch: string } | { error: string } {
|
|
30
|
+
const resolved = resolveBranch({ spec_path: specPath, plan_path: planPath, workspace_root: workspaceRoot });
|
|
31
|
+
if ("error" in resolved) return { error: resolved.error as string };
|
|
32
|
+
return { branch: resolved.branch };
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// CLI port of scripts/init/apply.sh.
|
|
2
|
+
import { initApplyData } from "../init";
|
|
3
|
+
|
|
4
|
+
const action = process.argv[2] ?? "";
|
|
5
|
+
const confirmed = process.argv[3] ?? "false";
|
|
6
|
+
if (confirmed !== "true") {
|
|
7
|
+
console.error("ERROR: confirmed=true required to write config");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
const out = initApplyData(action);
|
|
11
|
+
if (out.error) {
|
|
12
|
+
console.error("ERROR: " + out.error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
console.log(JSON.stringify(out));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// CLI port of scripts/pr-create.sh — create | --build-body.
|
|
2
|
+
import { prBuildBody, prCreate } from "../pr-create";
|
|
3
|
+
|
|
4
|
+
const mode = process.argv[2] ?? "create";
|
|
5
|
+
if (mode === "--build-body") {
|
|
6
|
+
console.log(JSON.stringify({ body: prBuildBody(process.env, process.cwd()) }));
|
|
7
|
+
process.exit(0);
|
|
8
|
+
}
|
|
9
|
+
if (process.env.WF_PR_CONFIRMED !== "true") {
|
|
10
|
+
console.error("ERROR: confirmed=true required");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
if (!process.env.WF_PR_TITLE) {
|
|
14
|
+
console.error("ERROR: title required");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const out = prCreate(process.env, process.cwd());
|
|
18
|
+
if (out.error || out.ok === false) {
|
|
19
|
+
console.log(JSON.stringify(out));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
console.log(JSON.stringify(out));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// CLI port of scripts/present/ascii-wireframe.sh — JSON spec on stdin.
|
|
2
|
+
import { renderAsciiWireframe } from "../present";
|
|
3
|
+
|
|
4
|
+
const input = await Bun.stdin.text();
|
|
5
|
+
try {
|
|
6
|
+
console.log(renderAsciiWireframe(input));
|
|
7
|
+
} catch (err) {
|
|
8
|
+
console.error(err instanceof Error ? err.message : "ascii-wireframe render failed");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// CLI port of scripts/present/flow-diagram.sh — JSON spec on stdin.
|
|
2
|
+
import { renderFlowDiagram } from "../present";
|
|
3
|
+
|
|
4
|
+
const input = await Bun.stdin.text();
|
|
5
|
+
try {
|
|
6
|
+
console.log(renderFlowDiagram(input));
|
|
7
|
+
} catch (err) {
|
|
8
|
+
console.error(err instanceof Error ? err.message : "flow-diagram render failed");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// CLI port of scripts/vcs/config.sh — mode: load | summary | resolve.
|
|
2
|
+
import { vcsConfig } from "../vcs-config";
|
|
3
|
+
|
|
4
|
+
const cmd = process.argv[2] ?? "load";
|
|
5
|
+
if (cmd !== "load" && cmd !== "summary" && cmd !== "resolve") {
|
|
6
|
+
console.error("ERROR: unknown command (load|summary|resolve)");
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
const out = vcsConfig(cmd, process.cwd());
|
|
10
|
+
if (!out.ok) {
|
|
11
|
+
console.log(JSON.stringify(out));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
console.log(JSON.stringify(out));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// CLI port of scripts/youtrack/api.sh — write guard before any config read.
|
|
2
|
+
import { youTrackApi } from "../youtrack";
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const cmd = args[0];
|
|
6
|
+
if (cmd === "log-time" || cmd === "post-comment") {
|
|
7
|
+
if ((process.env.WORKFLOW_YT_WRITE ?? "") !== "1") {
|
|
8
|
+
console.error("ERROR: YouTrack write operations require WORKFLOW_YT_WRITE=1 (refusing to mutate production)");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const out = youTrackApi(args, process.env.WORKFLOW_YT_WRITE ?? "");
|
|
13
|
+
if ("error" in out) {
|
|
14
|
+
console.log(JSON.stringify({ ok: false, error: out.error }));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
console.log(JSON.stringify(out.data));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// CLI port of scripts/youtrack/config.sh — JSON out, argv/env in.
|
|
2
|
+
import {
|
|
3
|
+
youTrackConfigLoad,
|
|
4
|
+
} from "../youtrack";
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const cmd = args[0] ?? "load";
|
|
8
|
+
|
|
9
|
+
if (cmd !== "load") {
|
|
10
|
+
console.error("ERROR: unknown command");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const out = youTrackConfigLoad();
|
|
15
|
+
if ("error" in out) {
|
|
16
|
+
if (out.error === "ERROR: missing youtrack.json") {
|
|
17
|
+
console.error(out.error);
|
|
18
|
+
} else {
|
|
19
|
+
console.log(JSON.stringify({ ok: false, error: out.error }));
|
|
20
|
+
}
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
console.log(JSON.stringify(out.data));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// CLI port of scripts/youtrack/greeting.sh.
|
|
2
|
+
import { youTrackGreeting } from "../youtrack";
|
|
3
|
+
|
|
4
|
+
const override = process.argv[2];
|
|
5
|
+
const result = youTrackGreeting(override);
|
|
6
|
+
if (result.exitCode !== 0) {
|
|
7
|
+
console.error(result.stderr || "greeting failed");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
process.stdout.write(result.stdout);
|