@brainervirus/workit-core 0.5.6 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -17
- package/package.json +9 -9
- package/scripts/_shared/common.sh +18 -3
- package/scripts/install-opencode-plugin.sh +14 -9
- package/scripts/lib/config-dir.sh +26 -0
- package/scripts/sync-runtime.sh +4 -1
- package/src/core/branch.ts +143 -48
- package/src/core/changelog.ts +17 -14
- package/src/core/config-guard.ts +9 -2
- package/src/core/config.ts +95 -15
- package/src/core/detector.ts +22 -11
- package/src/core/docs-repo.ts +50 -15
- package/src/core/docs-validate.ts +163 -37
- package/src/core/flow-state.ts +6 -2
- package/src/core/gitignore.ts +11 -2
- package/src/core/handoff-context.ts +18 -5
- package/src/core/hygiene.ts +27 -5
- package/src/core/init.ts +86 -21
- package/src/core/parse-sections.ts +2 -2
- package/src/core/plan-tasks.ts +13 -3
- package/src/core/ports/youtrack-api.ts +3 -1
- package/src/core/ports/youtrack-config.ts +1 -3
- package/src/core/pr-create.ts +47 -15
- package/src/core/present.ts +11 -2
- package/src/core/reminder.ts +1 -2
- package/src/core/repo-tool.ts +4 -1
- package/src/core/rules.ts +10 -7
- package/src/core/scripts.ts +7 -2
- package/src/core/sdd.ts +11 -3
- package/src/core/templates.ts +14 -4
- package/src/core/vcs-config.ts +94 -36
- package/src/core/verify-parse.ts +4 -2
- package/src/core/workspaces.ts +2 -2
- package/src/core/youtrack.ts +233 -58
- package/src/core.ts +18 -3
- package/src/tools/docs-repo.ts +12 -3
- package/src/tools/flow.ts +24 -13
- package/src/tools/handoff.ts +28 -23
- package/src/tools/present.ts +14 -10
- package/src/tools/repo.ts +220 -87
- package/src/tools/sdd.ts +93 -66
- package/src/tools/youtrack.ts +119 -52
- package/templates/superpowers-doc-contract.md +1 -1
package/src/tools/sdd.ts
CHANGED
|
@@ -5,15 +5,12 @@ import { fail, ok, resolveGitRevision, resolveInside } from "../core";
|
|
|
5
5
|
import { resolveBranch, docsBranch } from "../core/branch";
|
|
6
6
|
import { docsValidate } from "../core/docs-validate";
|
|
7
7
|
import { parsePlanTasks, resolveHandoffBranch } from "../core/plan-tasks";
|
|
8
|
-
import {
|
|
9
|
-
sddAppendProgress, sddContext, sddReviewPackage, sddTaskBrief,
|
|
10
|
-
} from "../core/sdd";
|
|
8
|
+
import { sddAppendProgress, sddContext, sddReviewPackage, sddTaskBrief } from "../core/sdd";
|
|
11
9
|
import { WorkflowStateStore } from "../state";
|
|
12
10
|
|
|
13
11
|
const output = (value: unknown) => JSON.stringify(value, null, 2);
|
|
14
|
-
const requireConfirmed = (confirmed: boolean) =>
|
|
15
|
-
? null
|
|
16
|
-
: output(fail("confirmed: true required"));
|
|
12
|
+
const requireConfirmed = (confirmed: boolean) =>
|
|
13
|
+
confirmed === true ? null : output(fail("confirmed: true required"));
|
|
17
14
|
|
|
18
15
|
const relativePath = (root: string, candidate: string) => {
|
|
19
16
|
if (path.isAbsolute(candidate)) throw new Error("path must be repository-relative");
|
|
@@ -49,89 +46,109 @@ const planPaths = (root: string, planPath: string, suppliedSpecPath?: string) =>
|
|
|
49
46
|
};
|
|
50
47
|
|
|
51
48
|
export function createSddTools(state: WorkflowStateStore) {
|
|
52
|
-
const record = (context: ToolContext, data: Record<string, unknown>) =>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
const record = (context: ToolContext, data: Record<string, unknown>) =>
|
|
50
|
+
state.set(context.sessionID, {
|
|
51
|
+
spec: String(data.spec_path ?? ""),
|
|
52
|
+
plan: String(data.plan_path ?? ""),
|
|
53
|
+
sdd: String(data.sdd_dir ?? ""),
|
|
54
|
+
});
|
|
57
55
|
|
|
58
56
|
return {
|
|
59
57
|
workflow_docs_branch: tool({
|
|
60
|
-
description:
|
|
58
|
+
description:
|
|
59
|
+
"Resolve branch for spec/plan authors: keep current feature|bugfix or create from the configured base",
|
|
61
60
|
args: {
|
|
62
61
|
plan_path: tool.schema.string().optional(),
|
|
63
62
|
kind: tool.schema.enum(["feature", "bugfix"]).optional(),
|
|
64
63
|
},
|
|
65
|
-
execute: async ({ plan_path, kind }, context) =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
execute: async ({ plan_path, kind }, context) =>
|
|
65
|
+
invoke(() => {
|
|
66
|
+
if (plan_path) relativePath(context.directory, plan_path);
|
|
67
|
+
return docsBranch({
|
|
68
|
+
plan_path,
|
|
69
|
+
kind,
|
|
70
|
+
workspace_root: context.directory,
|
|
71
|
+
}) as Record<string, unknown>;
|
|
72
|
+
}),
|
|
73
73
|
}),
|
|
74
74
|
workflow_docs_validate: tool({
|
|
75
|
-
description:
|
|
75
|
+
description:
|
|
76
|
+
"Hard-fail validate spec/plan headers, link, branch, task order; returns quality findings (hard/warning)",
|
|
76
77
|
args: {
|
|
77
78
|
spec_path: tool.schema.string(),
|
|
78
79
|
plan_path: tool.schema.string(),
|
|
79
80
|
},
|
|
80
|
-
execute: async ({ spec_path, plan_path }, context) =>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
execute: async ({ spec_path, plan_path }, context) =>
|
|
82
|
+
invoke(() => {
|
|
83
|
+
relativePath(context.directory, spec_path);
|
|
84
|
+
relativePath(context.directory, plan_path);
|
|
85
|
+
const result = docsValidate({
|
|
86
|
+
spec_path,
|
|
87
|
+
plan_path,
|
|
88
|
+
workspace_root: context.directory,
|
|
89
|
+
}) as Record<string, unknown>;
|
|
90
|
+
if (result.error) return result;
|
|
91
|
+
if (result.ok === false) return result;
|
|
92
|
+
return result;
|
|
93
|
+
}),
|
|
92
94
|
}),
|
|
93
95
|
workflow_plan_tasks: tool({
|
|
94
96
|
description: "Parse top-level tasks from a workflow plan",
|
|
95
97
|
args: { plan_path: tool.schema.string(), spec_path: tool.schema.string().optional() },
|
|
96
|
-
execute: async ({ plan_path, spec_path }, context) =>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
execute: async ({ plan_path, spec_path }, context) =>
|
|
99
|
+
invoke(() => {
|
|
100
|
+
relativePath(context.directory, plan_path);
|
|
101
|
+
const paths = planPaths(context.directory, plan_path, spec_path);
|
|
102
|
+
const parsed = parsePlanTasks(plan_path, context.directory) as Record<string, unknown>;
|
|
103
|
+
if (parsed.error) return parsed;
|
|
104
|
+
const branch = paths.spec_path
|
|
105
|
+
? (resolveHandoffBranch(paths.spec_path, plan_path, context.directory) as Record<
|
|
106
|
+
string,
|
|
107
|
+
unknown
|
|
108
|
+
>)
|
|
109
|
+
: {};
|
|
110
|
+
if (branch.error) return branch;
|
|
111
|
+
const data = { ...parsed, ...paths, ...branch };
|
|
112
|
+
record(context, data);
|
|
113
|
+
return data;
|
|
114
|
+
}),
|
|
109
115
|
}),
|
|
110
116
|
workflow_resolve_branch: tool({
|
|
111
117
|
description: "Resolve a branch from repository spec and plan metadata",
|
|
112
118
|
args: { spec_path: tool.schema.string(), plan_path: tool.schema.string() },
|
|
113
|
-
execute: async ({ spec_path, plan_path }, context) =>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
execute: async ({ spec_path, plan_path }, context) =>
|
|
120
|
+
invoke(() => {
|
|
121
|
+
relativePath(context.directory, spec_path);
|
|
122
|
+
relativePath(context.directory, plan_path);
|
|
123
|
+
return resolveBranch({ spec_path, plan_path, workspace_root: context.directory });
|
|
124
|
+
}),
|
|
118
125
|
}),
|
|
119
126
|
workflow_sdd_context: tool({
|
|
120
127
|
description: "Resolve the SDD workspace and progress ledger",
|
|
121
128
|
args: { plan_path: tool.schema.string() },
|
|
122
|
-
execute: async ({ plan_path }, context) =>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
execute: async ({ plan_path }, context) =>
|
|
130
|
+
invoke(() => {
|
|
131
|
+
relativePath(context.directory, plan_path);
|
|
132
|
+
const parsed = sddContext({
|
|
133
|
+
slug: undefined,
|
|
134
|
+
plan_path,
|
|
135
|
+
workspace_root: context.directory,
|
|
136
|
+
}) as Record<string, unknown>;
|
|
137
|
+
if (parsed.error) return parsed;
|
|
138
|
+
const todos = Array.isArray(parsed.todos)
|
|
139
|
+
? parsed.todos.map((todo: Record<string, unknown>) =>
|
|
140
|
+
todo.status === "in_progress" ? { ...todo, status: "pending" } : todo,
|
|
141
|
+
)
|
|
142
|
+
: [];
|
|
143
|
+
const data = {
|
|
144
|
+
...parsed,
|
|
145
|
+
todos,
|
|
146
|
+
...planPaths(context.directory, plan_path),
|
|
147
|
+
sdd_dir: parsed.sdd_dir,
|
|
148
|
+
};
|
|
149
|
+
record(context, data);
|
|
150
|
+
return data;
|
|
151
|
+
}),
|
|
135
152
|
}),
|
|
136
153
|
workflow_sdd_task_brief: tool({
|
|
137
154
|
description: "Write a confirmed task brief",
|
|
@@ -146,7 +163,12 @@ export function createSddTools(state: WorkflowStateStore) {
|
|
|
146
163
|
if (rejected) return rejected;
|
|
147
164
|
return invoke(() => {
|
|
148
165
|
relativePath(context.directory, sdd_dir);
|
|
149
|
-
return sddTaskBrief({
|
|
166
|
+
return sddTaskBrief({
|
|
167
|
+
sdd_dir,
|
|
168
|
+
task_id,
|
|
169
|
+
section_text,
|
|
170
|
+
workspace_root: context.directory,
|
|
171
|
+
});
|
|
150
172
|
});
|
|
151
173
|
},
|
|
152
174
|
}),
|
|
@@ -165,7 +187,12 @@ export function createSddTools(state: WorkflowStateStore) {
|
|
|
165
187
|
relativePath(context.directory, sdd_dir);
|
|
166
188
|
resolveGitRevision(context.directory, base_sha);
|
|
167
189
|
resolveGitRevision(context.directory, head_sha);
|
|
168
|
-
return sddReviewPackage({
|
|
190
|
+
return sddReviewPackage({
|
|
191
|
+
sdd_dir,
|
|
192
|
+
base_sha,
|
|
193
|
+
head_sha,
|
|
194
|
+
workspace_root: context.directory,
|
|
195
|
+
});
|
|
169
196
|
});
|
|
170
197
|
},
|
|
171
198
|
}),
|
package/src/tools/youtrack.ts
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { tool, type ToolContext } from "@opencode-ai/plugin";
|
|
5
5
|
import { fail, ok, resolveInside, type Result } from "../core";
|
|
6
6
|
import { configGuardError, describeConfigGaps } from "../core/config-guard";
|
|
7
|
+
import { configDir } from "../core/config";
|
|
7
8
|
import {
|
|
8
9
|
buildDraft as legacyBuildDraft,
|
|
9
10
|
context as legacyContext,
|
|
@@ -16,15 +17,18 @@ import {
|
|
|
16
17
|
|
|
17
18
|
const ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
18
19
|
const output = (value: unknown) => JSON.stringify(value, null, 2);
|
|
19
|
-
const message = (error: unknown) => error instanceof Error ? error.message : String(error);
|
|
20
|
+
const message = (error: unknown) => (error instanceof Error ? error.message : String(error));
|
|
20
21
|
|
|
21
22
|
// Both override names point at the config dir itself, same precedence as
|
|
22
23
|
// src/core/config.ts and scripts/init/status.sh: WORKFLOW_TOOLKIT_CONFIG → WORKFLOW_TOOLKIT_CONFIG_DIR → XDG.
|
|
24
|
+
// Default env (no args) routes through configDir() so the legacy migration runs.
|
|
23
25
|
export const configPath = (env: NodeJS.ProcessEnv = process.env, home = os.homedir()) =>
|
|
24
26
|
path.join(
|
|
25
|
-
env.
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
env === process.env
|
|
28
|
+
? configDir()
|
|
29
|
+
: (env.WORKFLOW_TOOLKIT_CONFIG ??
|
|
30
|
+
env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
31
|
+
path.join(env.XDG_CONFIG_HOME || path.join(home, ".config"), "workit")),
|
|
28
32
|
"youtrack.json",
|
|
29
33
|
);
|
|
30
34
|
|
|
@@ -33,7 +37,8 @@ export function readCredentials(env: NodeJS.ProcessEnv = process.env, home = os.
|
|
|
33
37
|
const config = JSON.parse(readFileSync(resolvedConfig, "utf8")) as { tokenFile?: string };
|
|
34
38
|
const tokenFile = config.tokenFile ?? "youtrack.token";
|
|
35
39
|
const tokenPath = path.resolve(path.dirname(resolvedConfig), tokenFile.replace(/^~(?=\/)/, home));
|
|
36
|
-
if (process.platform !== "win32" && (statSync(tokenPath).mode & 0o777) !== 0o600)
|
|
40
|
+
if (process.platform !== "win32" && (statSync(tokenPath).mode & 0o777) !== 0o600)
|
|
41
|
+
throw new Error("youtrack.token mode must be 0600");
|
|
37
42
|
const token = readFileSync(tokenPath, "utf8").trim();
|
|
38
43
|
if (!token) throw new Error("youtrack.token is empty");
|
|
39
44
|
return { configPath: resolvedConfig, token };
|
|
@@ -65,9 +70,13 @@ const defaultOperations: YouTrackOperations = {
|
|
|
65
70
|
verifyToken: () => unwrap(verifyYouTrackToken()),
|
|
66
71
|
context: (input) => legacyContext(input as never),
|
|
67
72
|
parseDuration: (text, workspaceRoot) => legacyParseDuration(text, workspaceRoot),
|
|
68
|
-
postComment: (issueId, markdown, workspaceRoot) =>
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
postComment: (issueId, markdown, workspaceRoot) =>
|
|
74
|
+
legacyPostUpdate({
|
|
75
|
+
confirmed: true,
|
|
76
|
+
issueId,
|
|
77
|
+
markdown,
|
|
78
|
+
workspace_root: workspaceRoot,
|
|
79
|
+
} as never),
|
|
71
80
|
logTime: (input) => legacyLogTime(input as never),
|
|
72
81
|
};
|
|
73
82
|
|
|
@@ -112,15 +121,26 @@ export async function postUpdate(
|
|
|
112
121
|
if (input.minutes != null && input.minutes <= 0) return fail("minutes must be positive");
|
|
113
122
|
|
|
114
123
|
try {
|
|
115
|
-
const comment = await operations.postComment(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
const comment = await operations.postComment(
|
|
125
|
+
input.issueId,
|
|
126
|
+
input.markdown,
|
|
127
|
+
input.workspace_root,
|
|
128
|
+
);
|
|
129
|
+
if (notApplied(comment))
|
|
130
|
+
return fail(comment.error, {
|
|
131
|
+
issueId: input.issueId,
|
|
132
|
+
postedComment: false,
|
|
133
|
+
loggedMinutes: 0,
|
|
134
|
+
outcome: "not_applied",
|
|
135
|
+
retry: "workflow_youtrack_post",
|
|
136
|
+
});
|
|
120
137
|
unwrap(comment);
|
|
121
138
|
} catch (error) {
|
|
122
139
|
return fail(message(error), {
|
|
123
|
-
issueId: input.issueId,
|
|
140
|
+
issueId: input.issueId,
|
|
141
|
+
postedComment: false,
|
|
142
|
+
loggedMinutes: 0,
|
|
143
|
+
outcome: "unknown",
|
|
124
144
|
instructions: "Check YouTrack comments manually; do not retry while the outcome is unknown.",
|
|
125
145
|
});
|
|
126
146
|
}
|
|
@@ -133,15 +153,23 @@ export async function postUpdate(
|
|
|
133
153
|
text: "workit update",
|
|
134
154
|
workspace_root: input.workspace_root,
|
|
135
155
|
});
|
|
136
|
-
if (notApplied(time))
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
156
|
+
if (notApplied(time))
|
|
157
|
+
return fail(time.error, {
|
|
158
|
+
issueId: input.issueId,
|
|
159
|
+
postedComment: true,
|
|
160
|
+
loggedMinutes: 0,
|
|
161
|
+
outcome: "not_applied",
|
|
162
|
+
retry: "workflow_youtrack_log_time",
|
|
163
|
+
});
|
|
140
164
|
unwrap(time);
|
|
141
165
|
} catch (error) {
|
|
142
166
|
return fail(message(error), {
|
|
143
|
-
issueId: input.issueId,
|
|
144
|
-
|
|
167
|
+
issueId: input.issueId,
|
|
168
|
+
postedComment: true,
|
|
169
|
+
loggedMinutes: 0,
|
|
170
|
+
outcome: "unknown",
|
|
171
|
+
instructions:
|
|
172
|
+
"Check YouTrack time entries manually; do not retry while the outcome is unknown.",
|
|
145
173
|
});
|
|
146
174
|
}
|
|
147
175
|
}
|
|
@@ -159,15 +187,21 @@ export async function logTimeUpdate(
|
|
|
159
187
|
): Promise<Result<Record<string, unknown>>> {
|
|
160
188
|
try {
|
|
161
189
|
const value = await operation.logTime(input);
|
|
162
|
-
if (notApplied(value))
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
190
|
+
if (notApplied(value))
|
|
191
|
+
return fail(value.error, {
|
|
192
|
+
issueId: input.issueId,
|
|
193
|
+
loggedMinutes: 0,
|
|
194
|
+
outcome: "not_applied",
|
|
195
|
+
retry: "workflow_youtrack_log_time",
|
|
196
|
+
});
|
|
166
197
|
return ok(unwrap(value));
|
|
167
198
|
} catch (error) {
|
|
168
199
|
return fail(message(error), {
|
|
169
|
-
issueId: input.issueId,
|
|
170
|
-
|
|
200
|
+
issueId: input.issueId,
|
|
201
|
+
loggedMinutes: 0,
|
|
202
|
+
outcome: "unknown",
|
|
203
|
+
instructions:
|
|
204
|
+
"Check YouTrack time entries manually; do not retry while the outcome is unknown.",
|
|
171
205
|
});
|
|
172
206
|
}
|
|
173
207
|
}
|
|
@@ -178,10 +212,13 @@ export function normalizeContext(value: LegacyValue, mode?: string): LegacyValue
|
|
|
178
212
|
const issue = String(config?.meetingIssue || "IRPT-12");
|
|
179
213
|
const { meetingIssues: _meetingIssues, ...singleMeetingConfig } = config ?? {};
|
|
180
214
|
const options = Array.isArray(value.meetingOptions)
|
|
181
|
-
? value.meetingOptions as Array<Record<string, unknown>>
|
|
215
|
+
? (value.meetingOptions as Array<Record<string, unknown>>)
|
|
182
216
|
: [];
|
|
183
217
|
const selected = options.find((option) => option.issue === issue) ?? {
|
|
184
|
-
key: "general",
|
|
218
|
+
key: "general",
|
|
219
|
+
issue,
|
|
220
|
+
label: issue,
|
|
221
|
+
workItemText: "Reuniones",
|
|
185
222
|
};
|
|
186
223
|
return {
|
|
187
224
|
...value,
|
|
@@ -216,9 +253,8 @@ const configGap = () => {
|
|
|
216
253
|
const { missing } = describeConfigGaps(["youtrack_json", "youtrack_token"]);
|
|
217
254
|
return missing.length > 0 ? output(fail(configGuardError(missing))) : null;
|
|
218
255
|
};
|
|
219
|
-
const requireConfirmed = (confirmed: boolean) =>
|
|
220
|
-
? null
|
|
221
|
-
: output(fail("confirmed: true required"));
|
|
256
|
+
const requireConfirmed = (confirmed: boolean) =>
|
|
257
|
+
confirmed === true ? null : output(fail("confirmed: true required"));
|
|
222
258
|
|
|
223
259
|
const rejectedTimeInput = (issueId: string, minutes: number) => {
|
|
224
260
|
const error = !ISSUE_RE.test(issueId)
|
|
@@ -226,11 +262,17 @@ const rejectedTimeInput = (issueId: string, minutes: number) => {
|
|
|
226
262
|
: !Number.isFinite(minutes) || minutes <= 0
|
|
227
263
|
? "minutes must be positive"
|
|
228
264
|
: null;
|
|
229
|
-
return error
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
265
|
+
return error
|
|
266
|
+
? output(
|
|
267
|
+
fail(error, {
|
|
268
|
+
issueId,
|
|
269
|
+
loggedMinutes: 0,
|
|
270
|
+
outcome: "not_applied",
|
|
271
|
+
retry: "workflow_youtrack_log_time",
|
|
272
|
+
instructions: "Correct the invalid input, then retry workflow_youtrack_log_time once.",
|
|
273
|
+
}),
|
|
274
|
+
)
|
|
275
|
+
: null;
|
|
234
276
|
};
|
|
235
277
|
|
|
236
278
|
export function createYouTrackTools(operations: YouTrackOperations = defaultOperations) {
|
|
@@ -240,7 +282,9 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
240
282
|
args: {},
|
|
241
283
|
execute: async () => {
|
|
242
284
|
let token = "";
|
|
243
|
-
try {
|
|
285
|
+
try {
|
|
286
|
+
token = credentials().token;
|
|
287
|
+
} catch (error) {
|
|
244
288
|
const gap = configGap();
|
|
245
289
|
if (gap) return gap;
|
|
246
290
|
return output(fail(message(error)));
|
|
@@ -254,7 +298,8 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
254
298
|
execute: async ({ issue_ref }) => invoke(() => parseIssueRef(issue_ref)),
|
|
255
299
|
}),
|
|
256
300
|
workflow_youtrack_context: tool({
|
|
257
|
-
description:
|
|
301
|
+
description:
|
|
302
|
+
"Load YouTrack context for the configured meeting issue or an existing task issue",
|
|
258
303
|
args: {
|
|
259
304
|
mode: tool.schema.enum(["meetings", "task"]).optional(),
|
|
260
305
|
issue_id: tool.schema.string().optional(),
|
|
@@ -271,23 +316,37 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
271
316
|
}
|
|
272
317
|
} catch (error) {
|
|
273
318
|
const detail = message(error);
|
|
274
|
-
return output(
|
|
319
|
+
return output(
|
|
320
|
+
fail(
|
|
321
|
+
detail.includes("repository-relative")
|
|
322
|
+
? detail
|
|
323
|
+
: `path must be repository-relative: ${detail}`,
|
|
324
|
+
),
|
|
325
|
+
);
|
|
275
326
|
}
|
|
276
327
|
let token = "";
|
|
277
|
-
try {
|
|
328
|
+
try {
|
|
329
|
+
token = credentials().token;
|
|
330
|
+
} catch (error) {
|
|
278
331
|
const gap = configGap();
|
|
279
332
|
if (gap) return gap;
|
|
280
333
|
return output(fail(message(error)));
|
|
281
334
|
}
|
|
282
|
-
return invoke(
|
|
283
|
-
|
|
284
|
-
|
|
335
|
+
return invoke(
|
|
336
|
+
async () =>
|
|
337
|
+
normalizeContext(
|
|
338
|
+
await operations.context({ ...input, workspace_root: context.directory }),
|
|
339
|
+
input.mode,
|
|
340
|
+
),
|
|
341
|
+
token,
|
|
342
|
+
);
|
|
285
343
|
},
|
|
286
344
|
}),
|
|
287
345
|
workflow_youtrack_parse_duration: tool({
|
|
288
346
|
description: "Parse duration text into integer minutes",
|
|
289
347
|
args: { text: tool.schema.string() },
|
|
290
|
-
execute: async ({ text }, context) =>
|
|
348
|
+
execute: async ({ text }, context) =>
|
|
349
|
+
invoke(() => operations.parseDuration(text, context.directory)),
|
|
291
350
|
}),
|
|
292
351
|
workflow_youtrack_draft: tool({
|
|
293
352
|
description: "Build an es-CL update comment without posting it",
|
|
@@ -298,10 +357,12 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
298
357
|
projectName: tool.schema.string().optional(),
|
|
299
358
|
includeProjectOpener: tool.schema.boolean().optional(),
|
|
300
359
|
includeFacts: tool.schema.boolean().optional(),
|
|
301
|
-
facts: tool.schema
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
360
|
+
facts: tool.schema
|
|
361
|
+
.object({
|
|
362
|
+
progress_excerpt: tool.schema.array(tool.schema.string()).optional(),
|
|
363
|
+
git_commits: tool.schema.array(tool.schema.string()).optional(),
|
|
364
|
+
})
|
|
365
|
+
.optional(),
|
|
305
366
|
},
|
|
306
367
|
execute: async (input) => invoke(() => legacyBuildDraft(input as never)),
|
|
307
368
|
}),
|
|
@@ -320,13 +381,16 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
320
381
|
const invalid = rejectedTimeInput(input.issueId, input.minutes);
|
|
321
382
|
if (invalid) return invalid;
|
|
322
383
|
let token = "";
|
|
323
|
-
try {
|
|
384
|
+
try {
|
|
385
|
+
token = credentials().token;
|
|
386
|
+
} catch (error) {
|
|
324
387
|
const gap = configGap();
|
|
325
388
|
if (gap) return gap;
|
|
326
389
|
return output(fail(message(error)));
|
|
327
390
|
}
|
|
328
391
|
const result = await withWriteFlag(() =>
|
|
329
|
-
logTimeUpdate({ ...input, workspace_root: context.directory }, operations)
|
|
392
|
+
logTimeUpdate({ ...input, workspace_root: context.directory }, operations),
|
|
393
|
+
);
|
|
330
394
|
return output(result.ok ? result : { ...result, error: redact(result.error, token) });
|
|
331
395
|
},
|
|
332
396
|
}),
|
|
@@ -342,13 +406,16 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
342
406
|
const rejected = requireConfirmed(input.confirmed);
|
|
343
407
|
if (rejected) return rejected;
|
|
344
408
|
let token = "";
|
|
345
|
-
try {
|
|
409
|
+
try {
|
|
410
|
+
token = credentials().token;
|
|
411
|
+
} catch (error) {
|
|
346
412
|
const gap = configGap();
|
|
347
413
|
if (gap) return gap;
|
|
348
414
|
return output(fail(message(error)));
|
|
349
415
|
}
|
|
350
416
|
const result = await withWriteFlag(() =>
|
|
351
|
-
postUpdate({ ...input, workspace_root: context.directory }, operations)
|
|
417
|
+
postUpdate({ ...input, workspace_root: context.directory }, operations),
|
|
418
|
+
);
|
|
352
419
|
return output(result.ok ? result : { ...result, error: redact(result.error, token) });
|
|
353
420
|
},
|
|
354
421
|
}),
|
|
@@ -27,7 +27,7 @@ Plans require:
|
|
|
27
27
|
|
|
28
28
|
`bugfix/<slug>` is also valid. Never use `main`, `develop`, `master`, or `prod`. Use plain backtick paths. Top-level headings are exactly `### Task N: Title`; steps use `- [ ] **Step N:** ...`; task headings never appear inside fences.
|
|
29
29
|
|
|
30
|
-
Before writing **Branch:** into a new spec or plan, call `workflow_docs_branch` and write the returned `branch` verbatim. When `action` is `keep`, use the current feature/bugfix branch. When `action` is `create_from_develop`, create the branch only through `workflow_branch_setup
|
|
30
|
+
Before writing **Branch:** into a new spec or plan, call `workflow_docs_branch` and write the returned `branch` verbatim. When `action` is `keep`, use the current feature/bugfix branch. When `action` is `create_from_develop` or `create_from_base`, create the branch only through `workflow_branch_setup`; it uses the configured workspace/global target branch.
|
|
31
31
|
|
|
32
32
|
## Execution and handoff
|
|
33
33
|
|