@tablation/crew 0.43.0 → 0.43.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/dist/cli.js +54 -2
- package/package.json +1 -1
- package/prompts/default/personas/lane-qa.md +6 -3
- package/prompts/default/skills/crew-lane-agent.md +15 -6
- package/prompts/default/skills/ticket-attachments.md +14 -8
- package/prompts/dev-qa/personas/lane-qa.md +6 -3
- package/prompts/dev-qa/skills/crew-lane-agent.md +15 -6
- package/prompts/dev-qa/skills/ticket-attachments.md +14 -8
package/dist/cli.js
CHANGED
|
@@ -3184,6 +3184,7 @@ var DEFAULT_CONTRACT = {
|
|
|
3184
3184
|
blockedBy: "blocked_by",
|
|
3185
3185
|
updatedAt: "updated_at",
|
|
3186
3186
|
needsDesign: "needs_design",
|
|
3187
|
+
attachments: "attachments",
|
|
3187
3188
|
workMode: "work_mode",
|
|
3188
3189
|
reportType: "report_type",
|
|
3189
3190
|
epic: "epic_id",
|
|
@@ -4786,6 +4787,7 @@ var Tracker = class {
|
|
|
4786
4787
|
* so concurrent first writes share one round of field-metadata reads.
|
|
4787
4788
|
*/
|
|
4788
4789
|
authorship;
|
|
4790
|
+
attachments;
|
|
4789
4791
|
stateDir;
|
|
4790
4792
|
constructor(route, ship) {
|
|
4791
4793
|
this.route = route;
|
|
@@ -5238,6 +5240,28 @@ var Tracker = class {
|
|
|
5238
5240
|
shipId: shipRow?.id ?? null
|
|
5239
5241
|
};
|
|
5240
5242
|
}
|
|
5243
|
+
/**
|
|
5244
|
+
* The attachments probe (CREW-1558): which table the Issues `attachments`
|
|
5245
|
+
* column references, read once per process like the authorship probe. On
|
|
5246
|
+
* TABL-1557 two runs reported a ticket's screenshot as a 404 because, with
|
|
5247
|
+
* no table id in hand, they guessed `files/<attachment id>` paths; naming
|
|
5248
|
+
* the table in the Environment section is what makes the lookup mechanical.
|
|
5249
|
+
*/
|
|
5250
|
+
attachmentsProbe() {
|
|
5251
|
+
this.attachments ??= this.probeAttachments();
|
|
5252
|
+
return this.attachments;
|
|
5253
|
+
}
|
|
5254
|
+
async probeAttachments() {
|
|
5255
|
+
try {
|
|
5256
|
+
const model = await this.client.dataModels.get(this.models.issues);
|
|
5257
|
+
const field = model.fields.find((f) => f.columnName === this.contract.columns.attachments);
|
|
5258
|
+
const fieldType = field?.fieldType;
|
|
5259
|
+
const target = fieldType?.targetModelId;
|
|
5260
|
+
return { mediaLibraryModelId: typeof target === "string" && target ? target : null };
|
|
5261
|
+
} catch {
|
|
5262
|
+
return { mediaLibraryModelId: null };
|
|
5263
|
+
}
|
|
5264
|
+
}
|
|
5241
5265
|
async columnNames(modelId) {
|
|
5242
5266
|
try {
|
|
5243
5267
|
const model = await this.client.dataModels.get(modelId);
|
|
@@ -6874,6 +6898,7 @@ function renderEnvironment(i) {
|
|
|
6874
6898
|
const only = repos.length === 1 ? repos[0] : void 0;
|
|
6875
6899
|
const models = route.resolved?.models;
|
|
6876
6900
|
const epicsModelId = route.resolved?.epicsModelId;
|
|
6901
|
+
const mediaLibraryModelId = i.attachments?.mediaLibraryModelId ?? null;
|
|
6877
6902
|
const key = "ISSUE-000";
|
|
6878
6903
|
const lines = [
|
|
6879
6904
|
"# Your environment",
|
|
@@ -6898,7 +6923,8 @@ function renderEnvironment(i) {
|
|
|
6898
6923
|
`- Issues: \`${models.issues}\``,
|
|
6899
6924
|
`- Comments: \`${models.comments}\``,
|
|
6900
6925
|
`- Crew: \`${models.crew}\``,
|
|
6901
|
-
...epicsModelId ? [`- Epics: \`${epicsModelId}\``] : []
|
|
6926
|
+
...epicsModelId ? [`- Epics: \`${epicsModelId}\``] : [],
|
|
6927
|
+
...mediaLibraryModelId ? [`- Media Library: \`${mediaLibraryModelId}\` (what a ticket's \`${contract.columns.attachments}\` ids point into)`] : []
|
|
6902
6928
|
);
|
|
6903
6929
|
}
|
|
6904
6930
|
const workspaceId = route.resolved?.workspaceId;
|
|
@@ -6925,6 +6951,30 @@ function renderEnvironment(i) {
|
|
|
6925
6951
|
"status and response body in your comment \u2014 do not write that uploads are",
|
|
6926
6952
|
"impossible."
|
|
6927
6953
|
);
|
|
6954
|
+
if (mediaLibraryModelId) {
|
|
6955
|
+
lines.push(
|
|
6956
|
+
"",
|
|
6957
|
+
"## Opening a ticket's attachments",
|
|
6958
|
+
"",
|
|
6959
|
+
`A ticket's or a comment's \`${contract.columns.attachments}\` value is a list of Media Library`,
|
|
6960
|
+
"**record ids** \u2014 not file ids and not URLs. `files/<id>`, `files/<id>/download` and",
|
|
6961
|
+
"`media-library/<id>/...` do not exist for such an id and return 404. For each id,",
|
|
6962
|
+
"read the record, then fetch the file by its storage key:",
|
|
6963
|
+
"",
|
|
6964
|
+
"```sh",
|
|
6965
|
+
`curl -s -H "Authorization: Bearer $${API_KEY_VAR}" -H "User-Agent: ${i.userAgent}" \\`,
|
|
6966
|
+
` "${base}/api/workspaces/${workspaceId}/data-models/${mediaLibraryModelId}/records/<attachment id>"`,
|
|
6967
|
+
'# \u2192 JSON with `storage_key` ("<uuid>/<filename>") and `mime_type`',
|
|
6968
|
+
`curl -s -o <scratch dir>/<filename> -H "Authorization: Bearer $${API_KEY_VAR}" -H "User-Agent: ${i.userAgent}" \\`,
|
|
6969
|
+
` "${base}/api/workspaces/${workspaceId}/files/<storage_key>"`,
|
|
6970
|
+
"```",
|
|
6971
|
+
"",
|
|
6972
|
+
"The MCP forms are `records_controller_find_one` (`dataModelId` = the Media Library",
|
|
6973
|
+
"id above) and `files_controller_download` (`uuid` and `filename` = the two halves",
|
|
6974
|
+
"of the storage key). Save the file outside the repository and open it; where it",
|
|
6975
|
+
"disagrees with the description, the picture is the reproduction."
|
|
6976
|
+
);
|
|
6977
|
+
}
|
|
6928
6978
|
}
|
|
6929
6979
|
if (i.shipName) {
|
|
6930
6980
|
lines.push(
|
|
@@ -11987,7 +12037,8 @@ async function buildEnvironment(route, ship, ticket) {
|
|
|
11987
12037
|
};
|
|
11988
12038
|
});
|
|
11989
12039
|
const authorship = await tracker.authorshipProbe().catch(() => null);
|
|
11990
|
-
|
|
12040
|
+
const attachments = await tracker.attachmentsProbe().catch(() => null);
|
|
12041
|
+
return renderEnvironment({ route, userAgent: ship.userAgent, repos, contract, sourceTicket: ticket, authorship, attachments, shipName: ship.name });
|
|
11991
12042
|
}
|
|
11992
12043
|
|
|
11993
12044
|
// src/cli.ts
|
|
@@ -12620,6 +12671,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12620
12671
|
contract,
|
|
12621
12672
|
sourceTicket: ticket,
|
|
12622
12673
|
authorship: await tracker.authorshipProbe().catch(() => null),
|
|
12674
|
+
attachments: await tracker.attachmentsProbe().catch(() => null),
|
|
12623
12675
|
shipName: cfg.ship.name
|
|
12624
12676
|
});
|
|
12625
12677
|
}
|
package/package.json
CHANGED
|
@@ -47,9 +47,12 @@ pass.
|
|
|
47
47
|
builder's closing comment — what it changed, how it says it verified,
|
|
48
48
|
what it admits it could not test — is the thing you are checking.
|
|
49
49
|
Anything it skipped is your first test. **Open the ticket's own
|
|
50
|
-
attachments first
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
attachments first**, exactly as Step 3.4 in the shared policy says: read
|
|
51
|
+
the Media Library record for its `storage_key`, then fetch
|
|
52
|
+
`files/<storage_key>` — never a guessed `files/<id>` path. The reporter's
|
|
53
|
+
screenshot is the repro you are judging the fix against. If you cannot
|
|
54
|
+
open one, say so with the exact request and response and set `needs_info`
|
|
55
|
+
rather than passing it.
|
|
53
56
|
4. **Get the branch and read the diff.** The branch on the remote is what
|
|
54
57
|
you test, and you never need the builder's worktree: cut your own with
|
|
55
58
|
`crew worktree <number>` (it prints the path), unless the builder's
|
|
@@ -284,12 +284,21 @@ before going further, and never change a ticket's `report_type` yourself.
|
|
|
284
284
|
4. **Post a short comment** on what you are about to do (Comments table:
|
|
285
285
|
`ticket_id`, `team_member_id` = your id, markdown `body`), and further
|
|
286
286
|
comments at meaningful milestones — the operator wants visible progress,
|
|
287
|
-
not just status flips. **Open the ticket's attachments before you start
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
`
|
|
292
|
-
|
|
287
|
+
not just status flips. **Open the ticket's attachments before you start.**
|
|
288
|
+
An `attachments` value on the ticket or a comment is a list of Media
|
|
289
|
+
Library record ids — not file ids and not URLs, so no `files/<id>` path
|
|
290
|
+
will ever work. For each id: (1) read the Media Library record,
|
|
291
|
+
`GET /api/workspaces/<workspaceId>/data-models/<Media Library id>/records/<id>`
|
|
292
|
+
(the Environment section's "Opening a ticket's attachments" gives the
|
|
293
|
+
table id), and take its `storage_key`; (2) fetch the bytes with
|
|
294
|
+
`GET /api/workspaces/<workspaceId>/files/<storage_key>` (the MCP form is
|
|
295
|
+
`files_controller_download` with `uuid` and `filename` being the two
|
|
296
|
+
halves of the storage key) into a scratch directory and open it; (3)
|
|
297
|
+
compare it with the description — where they disagree, the picture is the
|
|
298
|
+
reproduction. If you cannot open one, that is a blocker (comment naming
|
|
299
|
+
it and the exact failure, `needs_info` plus `needs_planning`, stop); never
|
|
300
|
+
proceed on the prose alone. The `Ticket Attachments` skill has the edge
|
|
301
|
+
cases.
|
|
293
302
|
5. **Implement the fix.** Read the relevant code first; do not guess at the
|
|
294
303
|
architecture. **Never bump the version or edit the changelog on this
|
|
295
304
|
branch** — the release phase assigns the version once per release, over
|
|
@@ -12,14 +12,20 @@ you picked up, and on any comment that has them, before you start.
|
|
|
12
12
|
|
|
13
13
|
1. **Read the Media Library record for that id.** The `attachments` column is
|
|
14
14
|
a reference into the workspace's Media Library table; its `storage_key`
|
|
15
|
-
and `mime_type` are what you need
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
is
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
and `mime_type` are what you need:
|
|
16
|
+
`GET /api/workspaces/<workspaceId>/data-models/<Media Library id>/records/<id>`
|
|
17
|
+
(MCP: `records_controller_find_one`). The Environment section's "Opening
|
|
18
|
+
a ticket's attachments" names the table id; if it is absent, find the
|
|
19
|
+
table by name in a workspace-level model listing — never by reading the
|
|
20
|
+
whole Issues model definition, which is over a megabyte. The id is a
|
|
21
|
+
record id, not a file id: `files/<id>`, `files/<id>/download` and
|
|
22
|
+
`media-library/<id>/...` all 404 by design.
|
|
23
|
+
2. **Fetch the bytes:** the `storage_key` has the form `<uuid>/<filename>`;
|
|
24
|
+
`GET /api/workspaces/<workspaceId>/files/<uuid>/<filename>` with the usual
|
|
25
|
+
headers streams it (the MCP form is `files_controller_download` with
|
|
26
|
+
`workspaceId`, `uuid` and `filename`, which hands an image back as a
|
|
27
|
+
viewable block). Save the file in a scratch directory outside the
|
|
28
|
+
repository, then open it.
|
|
23
29
|
3. **Compare what the picture shows with what the description says.** Where
|
|
24
30
|
they disagree, the picture is the reproduction.
|
|
25
31
|
|
|
@@ -46,9 +46,12 @@ pass.
|
|
|
46
46
|
builder's closing comment — what it changed, how it says it verified,
|
|
47
47
|
what it admits it could not test — is the thing you are checking.
|
|
48
48
|
Anything it skipped is your first test. **Open the ticket's own
|
|
49
|
-
attachments first
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
attachments first**, exactly as Step 3.4 in the shared policy says: read
|
|
50
|
+
the Media Library record for its `storage_key`, then fetch
|
|
51
|
+
`files/<storage_key>` — never a guessed `files/<id>` path. The reporter's
|
|
52
|
+
screenshot is the repro you are judging the fix against. If you cannot
|
|
53
|
+
open one, say so with the exact request and response and set `needs_info`
|
|
54
|
+
rather than passing it.
|
|
52
55
|
4. **Get the branch and read the diff.** The branch on the remote is what
|
|
53
56
|
you test, and you never need the builder's worktree: cut your own with
|
|
54
57
|
`crew worktree <number>` (it prints the path), unless the builder's
|
|
@@ -284,12 +284,21 @@ before going further, and never change a ticket's `report_type` yourself.
|
|
|
284
284
|
4. **Post a short comment** on what you are about to do (Comments table:
|
|
285
285
|
`ticket_id`, `team_member_id` = your id, markdown `body`), and further
|
|
286
286
|
comments at meaningful milestones — the operator wants visible progress,
|
|
287
|
-
not just status flips. **Open the ticket's attachments before you start
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
`
|
|
292
|
-
|
|
287
|
+
not just status flips. **Open the ticket's attachments before you start.**
|
|
288
|
+
An `attachments` value on the ticket or a comment is a list of Media
|
|
289
|
+
Library record ids — not file ids and not URLs, so no `files/<id>` path
|
|
290
|
+
will ever work. For each id: (1) read the Media Library record,
|
|
291
|
+
`GET /api/workspaces/<workspaceId>/data-models/<Media Library id>/records/<id>`
|
|
292
|
+
(the Environment section's "Opening a ticket's attachments" gives the
|
|
293
|
+
table id), and take its `storage_key`; (2) fetch the bytes with
|
|
294
|
+
`GET /api/workspaces/<workspaceId>/files/<storage_key>` (the MCP form is
|
|
295
|
+
`files_controller_download` with `uuid` and `filename` being the two
|
|
296
|
+
halves of the storage key) into a scratch directory and open it; (3)
|
|
297
|
+
compare it with the description — where they disagree, the picture is the
|
|
298
|
+
reproduction. If you cannot open one, that is a blocker (comment naming
|
|
299
|
+
it and the exact failure, `needs_info` plus `needs_planning`, stop); never
|
|
300
|
+
proceed on the prose alone. The `Ticket Attachments` skill has the edge
|
|
301
|
+
cases.
|
|
293
302
|
5. **Implement the fix.** Read the relevant code first; do not guess at the
|
|
294
303
|
architecture. **Never bump the version or edit the changelog on this
|
|
295
304
|
branch** — the release phase assigns the version once per release, over
|
|
@@ -12,14 +12,20 @@ you picked up, and on any comment that has them, before you start.
|
|
|
12
12
|
|
|
13
13
|
1. **Read the Media Library record for that id.** The `attachments` column is
|
|
14
14
|
a reference into the workspace's Media Library table; its `storage_key`
|
|
15
|
-
and `mime_type` are what you need
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
is
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
and `mime_type` are what you need:
|
|
16
|
+
`GET /api/workspaces/<workspaceId>/data-models/<Media Library id>/records/<id>`
|
|
17
|
+
(MCP: `records_controller_find_one`). The Environment section's "Opening
|
|
18
|
+
a ticket's attachments" names the table id; if it is absent, find the
|
|
19
|
+
table by name in a workspace-level model listing — never by reading the
|
|
20
|
+
whole Issues model definition, which is over a megabyte. The id is a
|
|
21
|
+
record id, not a file id: `files/<id>`, `files/<id>/download` and
|
|
22
|
+
`media-library/<id>/...` all 404 by design.
|
|
23
|
+
2. **Fetch the bytes:** the `storage_key` has the form `<uuid>/<filename>`;
|
|
24
|
+
`GET /api/workspaces/<workspaceId>/files/<uuid>/<filename>` with the usual
|
|
25
|
+
headers streams it (the MCP form is `files_controller_download` with
|
|
26
|
+
`workspaceId`, `uuid` and `filename`, which hands an image back as a
|
|
27
|
+
viewable block). Save the file in a scratch directory outside the
|
|
28
|
+
repository, then open it.
|
|
23
29
|
3. **Compare what the picture shows with what the description says.** Where
|
|
24
30
|
they disagree, the picture is the reproduction.
|
|
25
31
|
|