@brainervirus/workit-core 0.6.0 → 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/package.json +9 -9
- package/scripts/_shared/common.sh +18 -3
- package/scripts/install-opencode-plugin.sh +14 -9
- 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 +48 -15
- package/src/core/detector.ts +22 -11
- package/src/core/docs-repo.ts +49 -14
- 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 +93 -34
- package/src/core/verify-parse.ts +4 -2
- package/src/core/workspaces.ts +2 -2
- package/src/core/youtrack.ts +231 -56
- 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 +115 -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
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
|
|
18
18
|
const ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
19
19
|
const output = (value: unknown) => JSON.stringify(value, null, 2);
|
|
20
|
-
const message = (error: unknown) => error instanceof Error ? error.message : String(error);
|
|
20
|
+
const message = (error: unknown) => (error instanceof Error ? error.message : String(error));
|
|
21
21
|
|
|
22
22
|
// Both override names point at the config dir itself, same precedence as
|
|
23
23
|
// src/core/config.ts and scripts/init/status.sh: WORKFLOW_TOOLKIT_CONFIG → WORKFLOW_TOOLKIT_CONFIG_DIR → XDG.
|
|
@@ -26,9 +26,9 @@ export const configPath = (env: NodeJS.ProcessEnv = process.env, home = os.homed
|
|
|
26
26
|
path.join(
|
|
27
27
|
env === process.env
|
|
28
28
|
? configDir()
|
|
29
|
-
: env.WORKFLOW_TOOLKIT_CONFIG
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
: (env.WORKFLOW_TOOLKIT_CONFIG ??
|
|
30
|
+
env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
31
|
+
path.join(env.XDG_CONFIG_HOME || path.join(home, ".config"), "workit")),
|
|
32
32
|
"youtrack.json",
|
|
33
33
|
);
|
|
34
34
|
|
|
@@ -37,7 +37,8 @@ export function readCredentials(env: NodeJS.ProcessEnv = process.env, home = os.
|
|
|
37
37
|
const config = JSON.parse(readFileSync(resolvedConfig, "utf8")) as { tokenFile?: string };
|
|
38
38
|
const tokenFile = config.tokenFile ?? "youtrack.token";
|
|
39
39
|
const tokenPath = path.resolve(path.dirname(resolvedConfig), tokenFile.replace(/^~(?=\/)/, home));
|
|
40
|
-
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");
|
|
41
42
|
const token = readFileSync(tokenPath, "utf8").trim();
|
|
42
43
|
if (!token) throw new Error("youtrack.token is empty");
|
|
43
44
|
return { configPath: resolvedConfig, token };
|
|
@@ -69,9 +70,13 @@ const defaultOperations: YouTrackOperations = {
|
|
|
69
70
|
verifyToken: () => unwrap(verifyYouTrackToken()),
|
|
70
71
|
context: (input) => legacyContext(input as never),
|
|
71
72
|
parseDuration: (text, workspaceRoot) => legacyParseDuration(text, workspaceRoot),
|
|
72
|
-
postComment: (issueId, markdown, workspaceRoot) =>
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
postComment: (issueId, markdown, workspaceRoot) =>
|
|
74
|
+
legacyPostUpdate({
|
|
75
|
+
confirmed: true,
|
|
76
|
+
issueId,
|
|
77
|
+
markdown,
|
|
78
|
+
workspace_root: workspaceRoot,
|
|
79
|
+
} as never),
|
|
75
80
|
logTime: (input) => legacyLogTime(input as never),
|
|
76
81
|
};
|
|
77
82
|
|
|
@@ -116,15 +121,26 @@ export async function postUpdate(
|
|
|
116
121
|
if (input.minutes != null && input.minutes <= 0) return fail("minutes must be positive");
|
|
117
122
|
|
|
118
123
|
try {
|
|
119
|
-
const comment = await operations.postComment(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
+
});
|
|
124
137
|
unwrap(comment);
|
|
125
138
|
} catch (error) {
|
|
126
139
|
return fail(message(error), {
|
|
127
|
-
issueId: input.issueId,
|
|
140
|
+
issueId: input.issueId,
|
|
141
|
+
postedComment: false,
|
|
142
|
+
loggedMinutes: 0,
|
|
143
|
+
outcome: "unknown",
|
|
128
144
|
instructions: "Check YouTrack comments manually; do not retry while the outcome is unknown.",
|
|
129
145
|
});
|
|
130
146
|
}
|
|
@@ -137,15 +153,23 @@ export async function postUpdate(
|
|
|
137
153
|
text: "workit update",
|
|
138
154
|
workspace_root: input.workspace_root,
|
|
139
155
|
});
|
|
140
|
-
if (notApplied(time))
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
});
|
|
144
164
|
unwrap(time);
|
|
145
165
|
} catch (error) {
|
|
146
166
|
return fail(message(error), {
|
|
147
|
-
issueId: input.issueId,
|
|
148
|
-
|
|
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.",
|
|
149
173
|
});
|
|
150
174
|
}
|
|
151
175
|
}
|
|
@@ -163,15 +187,21 @@ export async function logTimeUpdate(
|
|
|
163
187
|
): Promise<Result<Record<string, unknown>>> {
|
|
164
188
|
try {
|
|
165
189
|
const value = await operation.logTime(input);
|
|
166
|
-
if (notApplied(value))
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
+
});
|
|
170
197
|
return ok(unwrap(value));
|
|
171
198
|
} catch (error) {
|
|
172
199
|
return fail(message(error), {
|
|
173
|
-
issueId: input.issueId,
|
|
174
|
-
|
|
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.",
|
|
175
205
|
});
|
|
176
206
|
}
|
|
177
207
|
}
|
|
@@ -182,10 +212,13 @@ export function normalizeContext(value: LegacyValue, mode?: string): LegacyValue
|
|
|
182
212
|
const issue = String(config?.meetingIssue || "IRPT-12");
|
|
183
213
|
const { meetingIssues: _meetingIssues, ...singleMeetingConfig } = config ?? {};
|
|
184
214
|
const options = Array.isArray(value.meetingOptions)
|
|
185
|
-
? value.meetingOptions as Array<Record<string, unknown>>
|
|
215
|
+
? (value.meetingOptions as Array<Record<string, unknown>>)
|
|
186
216
|
: [];
|
|
187
217
|
const selected = options.find((option) => option.issue === issue) ?? {
|
|
188
|
-
key: "general",
|
|
218
|
+
key: "general",
|
|
219
|
+
issue,
|
|
220
|
+
label: issue,
|
|
221
|
+
workItemText: "Reuniones",
|
|
189
222
|
};
|
|
190
223
|
return {
|
|
191
224
|
...value,
|
|
@@ -220,9 +253,8 @@ const configGap = () => {
|
|
|
220
253
|
const { missing } = describeConfigGaps(["youtrack_json", "youtrack_token"]);
|
|
221
254
|
return missing.length > 0 ? output(fail(configGuardError(missing))) : null;
|
|
222
255
|
};
|
|
223
|
-
const requireConfirmed = (confirmed: boolean) =>
|
|
224
|
-
? null
|
|
225
|
-
: output(fail("confirmed: true required"));
|
|
256
|
+
const requireConfirmed = (confirmed: boolean) =>
|
|
257
|
+
confirmed === true ? null : output(fail("confirmed: true required"));
|
|
226
258
|
|
|
227
259
|
const rejectedTimeInput = (issueId: string, minutes: number) => {
|
|
228
260
|
const error = !ISSUE_RE.test(issueId)
|
|
@@ -230,11 +262,17 @@ const rejectedTimeInput = (issueId: string, minutes: number) => {
|
|
|
230
262
|
: !Number.isFinite(minutes) || minutes <= 0
|
|
231
263
|
? "minutes must be positive"
|
|
232
264
|
: null;
|
|
233
|
-
return error
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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;
|
|
238
276
|
};
|
|
239
277
|
|
|
240
278
|
export function createYouTrackTools(operations: YouTrackOperations = defaultOperations) {
|
|
@@ -244,7 +282,9 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
244
282
|
args: {},
|
|
245
283
|
execute: async () => {
|
|
246
284
|
let token = "";
|
|
247
|
-
try {
|
|
285
|
+
try {
|
|
286
|
+
token = credentials().token;
|
|
287
|
+
} catch (error) {
|
|
248
288
|
const gap = configGap();
|
|
249
289
|
if (gap) return gap;
|
|
250
290
|
return output(fail(message(error)));
|
|
@@ -258,7 +298,8 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
258
298
|
execute: async ({ issue_ref }) => invoke(() => parseIssueRef(issue_ref)),
|
|
259
299
|
}),
|
|
260
300
|
workflow_youtrack_context: tool({
|
|
261
|
-
description:
|
|
301
|
+
description:
|
|
302
|
+
"Load YouTrack context for the configured meeting issue or an existing task issue",
|
|
262
303
|
args: {
|
|
263
304
|
mode: tool.schema.enum(["meetings", "task"]).optional(),
|
|
264
305
|
issue_id: tool.schema.string().optional(),
|
|
@@ -275,23 +316,37 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
275
316
|
}
|
|
276
317
|
} catch (error) {
|
|
277
318
|
const detail = message(error);
|
|
278
|
-
return output(
|
|
319
|
+
return output(
|
|
320
|
+
fail(
|
|
321
|
+
detail.includes("repository-relative")
|
|
322
|
+
? detail
|
|
323
|
+
: `path must be repository-relative: ${detail}`,
|
|
324
|
+
),
|
|
325
|
+
);
|
|
279
326
|
}
|
|
280
327
|
let token = "";
|
|
281
|
-
try {
|
|
328
|
+
try {
|
|
329
|
+
token = credentials().token;
|
|
330
|
+
} catch (error) {
|
|
282
331
|
const gap = configGap();
|
|
283
332
|
if (gap) return gap;
|
|
284
333
|
return output(fail(message(error)));
|
|
285
334
|
}
|
|
286
|
-
return invoke(
|
|
287
|
-
|
|
288
|
-
|
|
335
|
+
return invoke(
|
|
336
|
+
async () =>
|
|
337
|
+
normalizeContext(
|
|
338
|
+
await operations.context({ ...input, workspace_root: context.directory }),
|
|
339
|
+
input.mode,
|
|
340
|
+
),
|
|
341
|
+
token,
|
|
342
|
+
);
|
|
289
343
|
},
|
|
290
344
|
}),
|
|
291
345
|
workflow_youtrack_parse_duration: tool({
|
|
292
346
|
description: "Parse duration text into integer minutes",
|
|
293
347
|
args: { text: tool.schema.string() },
|
|
294
|
-
execute: async ({ text }, context) =>
|
|
348
|
+
execute: async ({ text }, context) =>
|
|
349
|
+
invoke(() => operations.parseDuration(text, context.directory)),
|
|
295
350
|
}),
|
|
296
351
|
workflow_youtrack_draft: tool({
|
|
297
352
|
description: "Build an es-CL update comment without posting it",
|
|
@@ -302,10 +357,12 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
302
357
|
projectName: tool.schema.string().optional(),
|
|
303
358
|
includeProjectOpener: tool.schema.boolean().optional(),
|
|
304
359
|
includeFacts: tool.schema.boolean().optional(),
|
|
305
|
-
facts: tool.schema
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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(),
|
|
309
366
|
},
|
|
310
367
|
execute: async (input) => invoke(() => legacyBuildDraft(input as never)),
|
|
311
368
|
}),
|
|
@@ -324,13 +381,16 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
324
381
|
const invalid = rejectedTimeInput(input.issueId, input.minutes);
|
|
325
382
|
if (invalid) return invalid;
|
|
326
383
|
let token = "";
|
|
327
|
-
try {
|
|
384
|
+
try {
|
|
385
|
+
token = credentials().token;
|
|
386
|
+
} catch (error) {
|
|
328
387
|
const gap = configGap();
|
|
329
388
|
if (gap) return gap;
|
|
330
389
|
return output(fail(message(error)));
|
|
331
390
|
}
|
|
332
391
|
const result = await withWriteFlag(() =>
|
|
333
|
-
logTimeUpdate({ ...input, workspace_root: context.directory }, operations)
|
|
392
|
+
logTimeUpdate({ ...input, workspace_root: context.directory }, operations),
|
|
393
|
+
);
|
|
334
394
|
return output(result.ok ? result : { ...result, error: redact(result.error, token) });
|
|
335
395
|
},
|
|
336
396
|
}),
|
|
@@ -346,13 +406,16 @@ export function createYouTrackTools(operations: YouTrackOperations = defaultOper
|
|
|
346
406
|
const rejected = requireConfirmed(input.confirmed);
|
|
347
407
|
if (rejected) return rejected;
|
|
348
408
|
let token = "";
|
|
349
|
-
try {
|
|
409
|
+
try {
|
|
410
|
+
token = credentials().token;
|
|
411
|
+
} catch (error) {
|
|
350
412
|
const gap = configGap();
|
|
351
413
|
if (gap) return gap;
|
|
352
414
|
return output(fail(message(error)));
|
|
353
415
|
}
|
|
354
416
|
const result = await withWriteFlag(() =>
|
|
355
|
-
postUpdate({ ...input, workspace_root: context.directory }, operations)
|
|
417
|
+
postUpdate({ ...input, workspace_root: context.directory }, operations),
|
|
418
|
+
);
|
|
356
419
|
return output(result.ok ? result : { ...result, error: redact(result.error, token) });
|
|
357
420
|
},
|
|
358
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
|
|