@iamlbccc/tdxd 1.1.0 → 1.2.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.
@@ -0,0 +1,371 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ usage() {
6
+ cat <<'EOF'
7
+ Usage:
8
+ paperclip-upload-artifact.sh FILE [options]
9
+
10
+ Uploads a generated file from the current workspace to the current Paperclip
11
+ issue, then creates an attachment-backed artifact work product by default.
12
+
13
+ Required environment for live uploads:
14
+ PAPERCLIP_API_URL, PAPERCLIP_API_KEY, PAPERCLIP_COMPANY_ID, PAPERCLIP_TASK_ID, PAPERCLIP_RUN_ID
15
+
16
+ Options:
17
+ --issue-id ID Issue id to attach to (default: PAPERCLIP_TASK_ID)
18
+ --company-id ID Company id (default: PAPERCLIP_COMPANY_ID)
19
+ --title TEXT Work product title (default: file basename)
20
+ --summary TEXT Work product summary
21
+ --content-type TYPE Override detected upload content type
22
+ --status STATUS Work product status (default: ready_for_review)
23
+ --no-work-product Only upload the issue attachment
24
+ --no-primary Do not mark the artifact work product primary for its type
25
+ --output FORMAT markdown or json (default: markdown)
26
+ --dry-run Print resolved upload settings without calling the API
27
+ --help, -h Show this help
28
+
29
+ Examples:
30
+ scripts/paperclip-upload-artifact.sh dist/demo.mp4 \
31
+ --title "Demo video render" \
32
+ --summary "MP4 render for board review"
33
+
34
+ scripts/paperclip-upload-artifact.sh out/walkthrough.webm \
35
+ --title "Walkthrough video" \
36
+ --content-type video/webm
37
+ EOF
38
+ }
39
+
40
+ require_command() {
41
+ if ! command -v "$1" >/dev/null 2>&1; then
42
+ printf 'Missing required command: %s\n' "$1" >&2
43
+ exit 1
44
+ fi
45
+ }
46
+
47
+ json_bool() {
48
+ if [[ "${1:-0}" == "1" ]]; then
49
+ printf 'true'
50
+ else
51
+ printf 'false'
52
+ fi
53
+ }
54
+
55
+ detect_content_type() {
56
+ local path="$1"
57
+ local lower
58
+ lower="$(printf '%s' "$path" | tr '[:upper:]' '[:lower:]')"
59
+
60
+ case "$lower" in
61
+ *.mp4|*.m4v) printf 'video/mp4' ;;
62
+ *.webm) printf 'video/webm' ;;
63
+ *.mov|*.qt) printf 'video/quicktime' ;;
64
+ *.png) printf 'image/png' ;;
65
+ *.jpg|*.jpeg) printf 'image/jpeg' ;;
66
+ *.gif) printf 'image/gif' ;;
67
+ *.webp) printf 'image/webp' ;;
68
+ *.svg) printf 'image/svg+xml' ;;
69
+ *.pdf) printf 'application/pdf' ;;
70
+ *.txt|*.log) printf 'text/plain' ;;
71
+ *.md|*.markdown) printf 'text/markdown' ;;
72
+ *.json) printf 'application/json' ;;
73
+ *.csv) printf 'text/csv' ;;
74
+ *.html|*.htm) printf 'text/html' ;;
75
+ *.zip) printf 'application/zip' ;;
76
+ *)
77
+ if command -v file >/dev/null 2>&1; then
78
+ file --brief --mime-type "$path"
79
+ else
80
+ printf 'application/octet-stream'
81
+ fi
82
+ ;;
83
+ esac
84
+ }
85
+
86
+ request_json() {
87
+ local method="$1"
88
+ local url="$2"
89
+ local body="${3:-}"
90
+ local response_file
91
+ local status_code
92
+
93
+ response_file="$(mktemp)"
94
+ if [[ -n "$body" ]]; then
95
+ status_code="$(
96
+ curl -sS -X "$method" -w '%{http_code}' -o "$response_file" \
97
+ "$url" \
98
+ -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
99
+ -H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
100
+ -H 'Content-Type: application/json' \
101
+ --data-binary "$body"
102
+ )"
103
+ else
104
+ status_code="$(
105
+ curl -sS -X "$method" -w '%{http_code}' -o "$response_file" \
106
+ "$url" \
107
+ -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
108
+ -H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID"
109
+ )"
110
+ fi
111
+
112
+ if [[ "$status_code" -lt 200 || "$status_code" -ge 300 ]]; then
113
+ printf 'Request failed (%s): %s\n' "$status_code" "$url" >&2
114
+ cat "$response_file" >&2
115
+ printf '\n' >&2
116
+ rm -f "$response_file"
117
+ exit 1
118
+ fi
119
+
120
+ cat "$response_file"
121
+ rm -f "$response_file"
122
+ }
123
+
124
+ upload_file() {
125
+ local url="$1"
126
+ local path="$2"
127
+ local content_type="$3"
128
+ local escaped_path
129
+ local response_file
130
+ local status_code
131
+
132
+ escaped_path="${path//\\/\\\\}"
133
+ escaped_path="${escaped_path//\"/\\\"}"
134
+ response_file="$(mktemp)"
135
+ status_code="$(
136
+ curl -sS -X POST -w '%{http_code}' -o "$response_file" \
137
+ "$url" \
138
+ -H "Authorization: Bearer $PAPERCLIP_API_KEY" \
139
+ -H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
140
+ -F "file=@\"${escaped_path}\";type=${content_type}"
141
+ )"
142
+
143
+ if [[ "$status_code" -lt 200 || "$status_code" -ge 300 ]]; then
144
+ printf 'Upload failed (%s): %s\n' "$status_code" "$url" >&2
145
+ cat "$response_file" >&2
146
+ printf '\n' >&2
147
+ rm -f "$response_file"
148
+ exit 1
149
+ fi
150
+
151
+ cat "$response_file"
152
+ rm -f "$response_file"
153
+ }
154
+
155
+ file_path=""
156
+ issue_id="${PAPERCLIP_TASK_ID:-}"
157
+ company_id="${PAPERCLIP_COMPANY_ID:-}"
158
+ title=""
159
+ summary=""
160
+ content_type=""
161
+ status="ready_for_review"
162
+ create_work_product=1
163
+ is_primary=1
164
+ output_format="markdown"
165
+ dry_run=0
166
+
167
+ while [[ $# -gt 0 ]]; do
168
+ case "$1" in
169
+ --issue-id)
170
+ issue_id="${2:-}"
171
+ shift 2
172
+ ;;
173
+ --company-id)
174
+ company_id="${2:-}"
175
+ shift 2
176
+ ;;
177
+ --title)
178
+ title="${2:-}"
179
+ shift 2
180
+ ;;
181
+ --summary)
182
+ summary="${2:-}"
183
+ shift 2
184
+ ;;
185
+ --content-type)
186
+ content_type="${2:-}"
187
+ shift 2
188
+ ;;
189
+ --status)
190
+ status="${2:-}"
191
+ shift 2
192
+ ;;
193
+ --no-work-product)
194
+ create_work_product=0
195
+ shift
196
+ ;;
197
+ --no-primary)
198
+ is_primary=0
199
+ shift
200
+ ;;
201
+ --output)
202
+ output_format="${2:-}"
203
+ shift 2
204
+ ;;
205
+ --dry-run)
206
+ dry_run=1
207
+ shift
208
+ ;;
209
+ --help|-h)
210
+ usage
211
+ exit 0
212
+ ;;
213
+ --*)
214
+ printf 'Unknown argument: %s\n' "$1" >&2
215
+ usage >&2
216
+ exit 1
217
+ ;;
218
+ *)
219
+ if [[ -n "$file_path" ]]; then
220
+ printf 'Unexpected positional argument: %s\n' "$1" >&2
221
+ usage >&2
222
+ exit 1
223
+ fi
224
+ file_path="$1"
225
+ shift
226
+ ;;
227
+ esac
228
+ done
229
+
230
+ if [[ -z "$file_path" ]]; then
231
+ printf 'Missing file path.\n' >&2
232
+ usage >&2
233
+ exit 1
234
+ fi
235
+
236
+ if [[ ! -f "$file_path" ]]; then
237
+ printf 'Artifact file does not exist: %s\n' "$file_path" >&2
238
+ exit 1
239
+ fi
240
+
241
+ if [[ "$output_format" != "markdown" && "$output_format" != "json" ]]; then
242
+ printf 'Unsupported output format: %s\n' "$output_format" >&2
243
+ exit 1
244
+ fi
245
+
246
+ require_command curl
247
+ require_command jq
248
+
249
+ if [[ -z "$title" ]]; then
250
+ title="$(basename "$file_path")"
251
+ fi
252
+
253
+ if [[ -z "$content_type" ]]; then
254
+ content_type="$(detect_content_type "$file_path")"
255
+ fi
256
+
257
+ if [[ "$dry_run" == "1" ]]; then
258
+ create_work_product_json="$(json_bool "$create_work_product")"
259
+ is_primary_json="$(json_bool "$is_primary")"
260
+ jq -n \
261
+ --arg file "$file_path" \
262
+ --arg issueId "$issue_id" \
263
+ --arg companyId "$company_id" \
264
+ --arg title "$title" \
265
+ --arg summary "$summary" \
266
+ --arg contentType "$content_type" \
267
+ --arg status "$status" \
268
+ --argjson createWorkProduct "$create_work_product_json" \
269
+ --argjson isPrimary "$is_primary_json" \
270
+ '{file: $file, issueId: $issueId, companyId: $companyId, title: $title, summary: $summary, contentType: $contentType, status: $status, createWorkProduct: $createWorkProduct, isPrimary: $isPrimary}'
271
+ exit 0
272
+ fi
273
+
274
+ if [[ -z "${PAPERCLIP_API_URL:-}" || -z "${PAPERCLIP_API_KEY:-}" || -z "${PAPERCLIP_RUN_ID:-}" ]]; then
275
+ printf 'Missing PAPERCLIP_API_URL, PAPERCLIP_API_KEY, or PAPERCLIP_RUN_ID.\n' >&2
276
+ exit 1
277
+ fi
278
+
279
+ if [[ -z "$issue_id" || -z "$company_id" ]]; then
280
+ printf 'Missing issue or company id. Pass --issue-id/--company-id or set PAPERCLIP_TASK_ID/PAPERCLIP_COMPANY_ID.\n' >&2
281
+ exit 1
282
+ fi
283
+
284
+ api_base="${PAPERCLIP_API_URL%/}/api"
285
+ attachment="$(
286
+ upload_file \
287
+ "$api_base/companies/$company_id/issues/$issue_id/attachments" \
288
+ "$file_path" \
289
+ "$content_type"
290
+ )"
291
+
292
+ work_product="null"
293
+ if [[ "$create_work_product" == "1" ]]; then
294
+ is_primary_json="$(json_bool "$is_primary")"
295
+ attachment_id="$(jq -r '.id // empty' <<<"$attachment")"
296
+ byte_size="$(jq -r '.byteSize // 0' <<<"$attachment")"
297
+ content_path="$(jq -r '.contentPath // empty' <<<"$attachment")"
298
+ open_path="$(jq -r '.openPath // .contentPath // empty' <<<"$attachment")"
299
+ download_path="$(jq -r '.downloadPath // (if .contentPath then (.contentPath + "?download=1") else "" end)' <<<"$attachment")"
300
+ original_filename="$(jq -r '.originalFilename // empty' <<<"$attachment")"
301
+
302
+ if [[ -z "$attachment_id" || -z "$content_path" || -z "$download_path" ]]; then
303
+ printf 'Upload response did not include attachment path metadata.\n' >&2
304
+ printf '%s\n' "$attachment" >&2
305
+ exit 1
306
+ fi
307
+
308
+ work_product_payload="$(
309
+ jq -nc \
310
+ --arg title "$title" \
311
+ --arg summary "$summary" \
312
+ --arg status "$status" \
313
+ --arg runId "$PAPERCLIP_RUN_ID" \
314
+ --arg attachmentId "$attachment_id" \
315
+ --arg contentType "$content_type" \
316
+ --argjson byteSize "$byte_size" \
317
+ --arg contentPath "$content_path" \
318
+ --arg openPath "$open_path" \
319
+ --arg downloadPath "$download_path" \
320
+ --arg originalFilename "$original_filename" \
321
+ --argjson isPrimary "$is_primary_json" \
322
+ '{
323
+ type: "artifact",
324
+ provider: "paperclip",
325
+ title: $title,
326
+ status: $status,
327
+ reviewState: "none",
328
+ isPrimary: $isPrimary,
329
+ healthStatus: "unknown",
330
+ summary: (if $summary == "" then null else $summary end),
331
+ createdByRunId: $runId,
332
+ metadata: {
333
+ attachmentId: $attachmentId,
334
+ contentType: $contentType,
335
+ byteSize: $byteSize,
336
+ contentPath: $contentPath,
337
+ openPath: $openPath,
338
+ downloadPath: $downloadPath,
339
+ originalFilename: (if $originalFilename == "" then null else $originalFilename end)
340
+ }
341
+ }'
342
+ )"
343
+
344
+ work_product="$(
345
+ request_json \
346
+ POST \
347
+ "$api_base/issues/$issue_id/work-products" \
348
+ "$work_product_payload"
349
+ )"
350
+ fi
351
+
352
+ if [[ "$output_format" == "json" ]]; then
353
+ jq -n --argjson attachment "$attachment" --argjson workProduct "$work_product" \
354
+ '{attachment: $attachment, workProduct: $workProduct}'
355
+ exit 0
356
+ fi
357
+
358
+ content_path="$(jq -r '.contentPath // empty' <<<"$attachment")"
359
+ download_path="$(jq -r '.downloadPath // (if .contentPath then (.contentPath + "?download=1") else "" end)' <<<"$attachment")"
360
+ attachment_id="$(jq -r '.id // empty' <<<"$attachment")"
361
+ work_product_id="$(jq -r '.id // empty' <<<"$work_product")"
362
+
363
+ printf 'Uploaded artifact\n\n'
364
+ printf -- '- Attachment: [%s](%s)\n' "$title" "$content_path"
365
+ printf -- '- Download: [%s](%s)\n' "$title" "$download_path"
366
+ printf -- '- Attachment ID: `%s`\n' "$attachment_id"
367
+ if [[ -n "$work_product_id" ]]; then
368
+ printf -- '- Work product ID: `%s`\n' "$work_product_id"
369
+ fi
370
+ printf '\nFinal comment snippet:\n\n'
371
+ printf -- '- Artifact: [%s](%s)\n' "$title" "$content_path"
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: paperclip-converting-plans-to-tasks
3
+ description: >
4
+ Convert Paperclip plans into executable issue graphs. Use when asked to plan,
5
+ scope, or break down Paperclip company work into assigned tasks with specialty
6
+ fit, dependencies, blockers, and parallelization.
7
+ ---
8
+
9
+ # Paperclip — Converting Plans to Tasks
10
+
11
+ A companion skill for turning a plan into executable Paperclip work. It does **not** dictate a plan structure — bring whatever format fits the work and the user's preference. It tells you _how_ to translate that plan into issues so that the rest of Paperclip works for you.
12
+
13
+ For the **mechanics** of recording a plan (issue document with key `plan`, comment links, approval gating, who to reassign back to), follow the _Planning_ section of the `paperclip` skill. This skill covers planning method, not the API surface.
14
+
15
+ ## When you're asked to plan
16
+
17
+ - **Plan deeply.** Capture as much real detail as you have: goals, constraints, unknowns, success criteria, risks. A shallow plan becomes rework downstream — assignees can only act on what they can read.
18
+ - **Minimize the issue graph.** Use as few tasks as possible while still completing and verifying the job. Prefer one end-to-end task with one owner over separate tasks for each step, file, component, or phase. Keep those structural details as checklists or acceptance criteria inside the owning task unless a real execution boundary requires another issue.
19
+ - **Split only for a qualifying boundary.** Create a separate subtask only when at least one of these applies:
20
+ - A different specialist, owner, permission boundary, or external actor must own the work.
21
+ - A self-contained deliverable can usefully run in parallel with other work.
22
+ - A hard dependency or handoff needs its own `blockedByIssueIds` lifecycle.
23
+ - A review, QA pass, or governed approval gate has an independent owner.
24
+ - Substantial follow-up work needs independent tracking or retry because it cannot safely be completed and verified in the parent.
25
+ - **Know your team.** Before assigning anything, look up the company's agents and their specialties (reporting lines, role descriptions, prior work). Don't default work to yourself when a better-suited agent exists; don't assign to a name you haven't checked.
26
+ - **Assign for specialty.** Hand each piece of work to the agent most relevant to it. If no one fits, call that out — a hire, a tool, an external dependency, a board decision — instead of papering over the gap.
27
+ - **Take responsibility.** Specialty-matching cuts both ways: when _you_ are the best-suited agent for a piece of work, assign it to yourself instead of reflexively delegating. Don't hand off to avoid load.
28
+ - **Use the dependency tree.** Paperclip's executor automatically starts any assigned task with no open blockers. Parent/child issue nesting is structure, not execution blocking. Express each qualifying ownership or lifecycle boundary as an issue; keep other concrete deliverables within the responsible issue's description, checklist, or acceptance criteria. Wire every hard dependency between issues through `blockedByIssueIds` on the dependent issue (not prose like "blocked by X"). When a blocker reaches `done`, dependents auto-wake.
29
+ - **Order, then parallelize.** Sequence work by real dependencies, not by personal preference. Create parallel branches only for qualifying, self-contained work, then start those independent branches in parallel. Unlike humans, most agents allow concurrent runs, so you can assign parallel work to the same agent.
30
+ - **Write review tasks for the reviewer's boundary.** A review/QA task must tell the delegate to post findings on **their own review issue** and mark it `done` — the verdict is the deliverable, and adverse findings are still `done`, not `blocked`. Never instruct a delegate to comment on the parent issue (low-trust reviewers are guaranteed a 403 there), and make the description self-contained since the reviewer may not be able to read your issue. Wire the dependent issue's `blockedByIssueIds` to the review issue so the verdict wakes the right owner.
31
+ - **Enough is enough.** Plans exist to unblock execution, not replace it. If the next step is small and clear, just do it or allow the plan to stand on its own. Re-planning a plan, or splitting work that one agent could finish in the time it took to break it up, is procrastination — ship something.
32
+
33
+ ## When converting an accepted plan into tasks
34
+
35
+ Start from one end-to-end task and add issues only for the qualifying boundaries above. Before creating tasks, write a compact task matrix with each proposed task, owner, initial status, blockers, and the specific qualifying reason it must be separate. Any task that can start immediately should say why it has no blockers; otherwise set it to `blocked` and include the prerequisite issue IDs in `blockedByIssueIds`. Do not rely on `parentId`, child ordering, phase labels, or prose to block execution.
36
+
37
+ Run a merge-back pass before publishing or creating the graph. Require every proposed subtask to name at least one qualifying reason from this skill. If it cannot, merge it into its parent or an adjacent task and preserve the work as an internal step, checklist item, or acceptance criterion. Repeat until every remaining issue has a real ownership, scheduling, lifecycle, or governance reason to exist.
38
+
39
+ After creating the tasks, re-fetch the created issues or otherwise verify the issue graph before marking the source planning issue done. Confirm that every separate issue still has its qualifying reason, each dependent task has the expected `blockedByIssueIds`, each independent task has an explicit "can start now" reason, review tasks respect the reviewer's write boundary, and the parent/child hierarchy is only being used for traceability. If the graph contains an unjustified split or expected blockers are missing, correct it or report the mismatch and leave the planning issue in `in_review` or `blocked` until the graph is fixed.
40
+
41
+ ## Quick checklist before you publish a plan
42
+
43
+ - [ ] Enough detail that assignees can act without re-asking.
44
+ - [ ] The plan uses the fewest tasks that can complete and verify the job, preferring one end-to-end owner over step/file/component/phase splits.
45
+ - [ ] Every concrete deliverable is accounted for inside an issue or, only when a qualifying boundary applies, as its own issue.
46
+ - [ ] Every proposed subtask names a qualifying reason; otherwise it was merged into its parent or an adjacent task.
47
+ - [ ] Each issue has a deliberate, specialty-matched assignee — not the planner by default.
48
+ - [ ] Each issue's real blockers are declared via `blockedByIssueIds`.
49
+ - [ ] Independently owned review, QA, and governed approval tasks respect the reviewer's boundary.
50
+ - [ ] A compact task matrix names planned task, owner, initial status, blockers, and qualifying reason.
51
+ - [ ] Tasks without blockers have an explicit reason they can start immediately.
52
+ - [ ] Created issues were re-fetched or otherwise verified before closing the source planning issue.
53
+ - [ ] Qualifying independent branches can start in parallel.
54
+ - [ ] Gaps (missing skills, hires, decisions, external inputs) are surfaced, not hidden.
55
+
56
+ ## What this skill is not
57
+
58
+ - Not a plan template. Use any format — prose, outline, table, RACI, Gantt, whatever fits.
59
+ - Not software-development–specific. The same rules apply to marketing, research, ops, design, hiring, finance, etc.
60
+ - Not a replacement for the `paperclip` skill's planning mechanics. Use both.