@raquezha/norpiv 0.0.6 → 0.0.8

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.
@@ -120,22 +120,26 @@ PY
120
120
 
121
121
  write_active_pointer() {
122
122
  mkdir -p ".workflow"
123
- python3 - ".workflow/active_task.json" "$TASK_FOLDER" "$SOURCE" "$ID" "$TASK_DIR" "$BRANCH_NAME" <<'PY'
123
+ python3 - ".workflow/active.json" "$TASK_FOLDER" "$SOURCE" "$ID" "$TASK_DIR" "$WORK_MD" "$BRANCH_NAME" "$ISO_NOW" <<'PY'
124
124
  import json
125
125
  import sys
126
126
  from pathlib import Path
127
127
 
128
- path, active_task, source, raw_id, task_path, branch = sys.argv[1:]
129
- data = {
130
- "active_task": active_task,
128
+ active_workflow_path, active_task, source, raw_id, task_path, state_file, branch, now = sys.argv[1:]
129
+ workflow_data = {
130
+ "workflow": "rpiv",
131
+ "id": active_task,
132
+ "taskId": active_task,
131
133
  "source": source,
132
- "id": raw_id,
133
134
  "sourceId": raw_id,
135
+ "stateFile": state_file,
134
136
  "taskPath": task_path,
135
137
  "path": task_path,
136
138
  "branch": branch,
139
+ "startedAt": now,
140
+ "updatedAt": now,
137
141
  }
138
- Path(path).write_text(json.dumps(data, indent=2) + "\n")
142
+ Path(active_workflow_path).write_text(json.dumps(workflow_data, indent=2) + "\n")
139
143
  PY
140
144
  }
141
145
 
@@ -187,24 +191,43 @@ PY
187
191
  }
188
192
 
189
193
  update_work_meta() {
190
- local status phase
194
+ local status
191
195
  status=$(json_get "$METADATA_JSON" status)
192
- phase=$(json_get "$METADATA_JSON" phase)
193
196
  status=${status:-active}
194
- phase=${phase:-triaged}
195
197
 
196
- python3 - "$WORK_MD" "$BRANCH_NAME" "$status" "$phase" "$SOURCE" "$ID" <<'PY'
198
+ python3 - "$WORK_MD" "$BRANCH_NAME" "$status" "$SOURCE" "$ID" <<'PY'
197
199
  import re
198
200
  import sys
199
201
  from pathlib import Path
200
202
 
201
203
  path = Path(sys.argv[1])
202
- branch, status, phase, source, raw_id = sys.argv[2:]
204
+ branch, status, source, raw_id = sys.argv[2:]
203
205
  text = path.read_text() if path.exists() else ""
204
206
  lines = text.splitlines()
205
207
  header_re = re.compile(r"^(## )?\[[A-Z0-9_-]+\]\s*$")
206
208
  meta_re = re.compile(r"^(## )?\[META\]\s*$")
207
209
 
210
+
211
+ def section_body(section):
212
+ header = re.compile(rf"^(## )?\[{re.escape(section)}\]\s*$")
213
+ start = next((i for i, line in enumerate(lines) if header.match(line)), None)
214
+ if start is None:
215
+ return ""
216
+ end = len(lines)
217
+ for i in range(start + 1, len(lines)):
218
+ if header_re.match(lines[i]):
219
+ end = i
220
+ break
221
+ return "\n".join(lines[start + 1:end]).strip()
222
+
223
+
224
+ def meaningful(section):
225
+ body = section_body(section)
226
+ return any(line.strip() and line.strip() not in {"-", "- [ ]"} for line in body.splitlines())
227
+
228
+
229
+ phase = "planned" if meaningful("PLAN") else "grilled" if meaningful("GRILL") else "framed" if meaningful("BRIEF") else "triaged"
230
+
208
231
  new_block = [
209
232
  "## [META]",
210
233
  f"- Branch: `{branch}`",
@@ -232,6 +255,100 @@ path.write_text("\n".join(updated).rstrip() + "\n")
232
255
  PY
233
256
  }
234
257
 
258
+ write_work_snapshot() {
259
+ python3 - "$WORK_MD" "$METADATA_JSON" "$SOURCE" "$ID" <<'PY'
260
+ import json
261
+ import re
262
+ import sys
263
+ from pathlib import Path
264
+
265
+ work_md, metadata_json, source, raw_id = sys.argv[1:]
266
+
267
+ try:
268
+ metadata = json.loads(Path(metadata_json).read_text())
269
+ if not isinstance(metadata, dict):
270
+ metadata = {}
271
+ except Exception:
272
+ metadata = {}
273
+
274
+
275
+ def pick(*values, fallback=""):
276
+ for value in values:
277
+ if isinstance(value, str) and value.strip():
278
+ return value.strip()
279
+ return fallback
280
+
281
+
282
+ def squash(value):
283
+ if isinstance(value, dict):
284
+ value = json.dumps(value, ensure_ascii=False)
285
+ if not isinstance(value, str):
286
+ return ""
287
+ text = re.sub(r"```.*?```", " ", value, flags=re.S)
288
+ text = re.sub(r"^(## )?\[[A-Z0-9_-]+\]\s*$", " ", text, flags=re.M)
289
+ text = re.sub(r"\s+", " ", text).strip()
290
+ return text[:280].rstrip() + ("..." if len(text) > 280 else "")
291
+
292
+
293
+ fields = metadata.get("fields") if isinstance(metadata.get("fields"), dict) else {}
294
+ title = {
295
+ "github": pick(metadata.get("title"), fallback=f"GitHub #{raw_id}"),
296
+ "gitlab": pick(metadata.get("title"), fallback=f"GitLab #{raw_id}"),
297
+ "jira": pick(fields.get("summary"), metadata.get("key"), fallback=f"Jira {raw_id}"),
298
+ }.get(source, pick(metadata.get("title"), fallback=f"Local Task {raw_id}"))
299
+ url = {
300
+ "github": pick(metadata.get("url"), fallback=f"https://github.com/issues/{raw_id}"),
301
+ "gitlab": pick(metadata.get("web_url"), metadata.get("url")),
302
+ "jira": pick(metadata.get("self")),
303
+ }.get(source, "")
304
+ summary = {
305
+ "github": squash(metadata.get("body")),
306
+ "gitlab": squash(metadata.get("description")),
307
+ "jira": squash(fields.get("description")),
308
+ }.get(source, squash(metadata.get("body"))) or title
309
+ tracker_updated = {
310
+ "github": pick(metadata.get("updatedAt"), metadata.get("createdAt")),
311
+ "gitlab": pick(metadata.get("updated_at"), metadata.get("created_at")),
312
+ "jira": pick(fields.get("updated"), metadata.get("updated")),
313
+ }.get(source, "")
314
+
315
+ lines = [
316
+ f"# WORK: {title}",
317
+ "",
318
+ "## [INTAKE]",
319
+ "### Outcome",
320
+ summary,
321
+ "",
322
+ "### Acceptance Criteria",
323
+ "- [ ] Confirm concrete acceptance criteria from tracker or refine them during `/frame`.",
324
+ "",
325
+ "### Scope / Non-goals",
326
+ "- Keep local execution state concise; do not copy raw tracker or CLI rendering into `WORK.md`.",
327
+ "",
328
+ "### Dependencies / Blockers",
329
+ "- None noted from intake snapshot.",
330
+ "",
331
+ "### Tracker Context",
332
+ f"- Task: `{source}:{raw_id}`",
333
+ f"- URL: {url}" if url else "- URL: None noted",
334
+ f"- Tracker updated: `{tracker_updated}`" if tracker_updated else "- Tracker updated: None noted",
335
+ "",
336
+ "## [BRIEF]",
337
+ "- ",
338
+ "",
339
+ "## [GRILL]",
340
+ "- ",
341
+ "",
342
+ "## [PLAN]",
343
+ "- [ ] ",
344
+ "",
345
+ "## [LOG]",
346
+ ]
347
+
348
+ Path(work_md).write_text("\n".join(lines).rstrip() + "\n")
349
+ PY
350
+ }
351
+
235
352
  backfill_metadata() {
236
353
  json_upsert "$METADATA_JSON" \
237
354
  "id=$ID" \
@@ -239,21 +356,18 @@ backfill_metadata() {
239
356
  "branch=$BRANCH_NAME" \
240
357
  "taskFolder=$TASK_FOLDER" \
241
358
  "?status=active" \
242
- "?phase=triaged" \
243
359
  "createdAt=$ISO_NOW" \
244
360
  "updatedAt=$ISO_NOW"
245
361
  }
246
362
 
247
363
  set_metadata_status_phase() {
248
364
  local status="$1"
249
- local phase="$2"
250
365
  json_upsert "$METADATA_JSON" \
251
366
  "id=$ID" \
252
367
  "source=$SOURCE" \
253
368
  "branch=$BRANCH_NAME" \
254
369
  "taskFolder=$TASK_FOLDER" \
255
370
  "status=$status" \
256
- "phase=$phase" \
257
371
  "createdAt=$ISO_NOW" \
258
372
  "updatedAt=$ISO_NOW"
259
373
  }
@@ -266,24 +380,25 @@ create_task() {
266
380
  github)
267
381
  command -v gh >/dev/null 2>&1 || { echo "ERROR: gh CLI is required for github tasks."; exit 1; }
268
382
  echo "Fetching GitHub Issue #$ID..."
269
- gh issue view "$ID" --json title,body,author,labels,comments > "$METADATA_JSON"
270
- echo "# WORK: GitHub #$ID" > "$WORK_MD"
271
- gh issue view "$ID" >> "$WORK_MD"
383
+ gh issue view "$ID" --json title,body,author,labels,url,number,createdAt,updatedAt > "$METADATA_JSON"
272
384
  ;;
273
385
  gitlab)
274
386
  command -v glab >/dev/null 2>&1 || { echo "ERROR: glab CLI is required for gitlab tasks."; exit 1; }
275
387
  echo "Fetching GitLab Issue #$ID..."
276
- glab issue view "$ID" > "$WORK_MD"
277
- echo '{}' > "$METADATA_JSON"
388
+ if ! glab issue view "$ID" --output json > "$METADATA_JSON" 2>/dev/null; then
389
+ echo '{}' > "$METADATA_JSON"
390
+ json_upsert "$METADATA_JSON" "title=GitLab Issue $ID"
391
+ fi
278
392
  ;;
279
393
  jira)
280
394
  echo "Fetching Jira Ticket $ID..."
281
395
  if command -v jira >/dev/null 2>&1; then
282
- jira issue view "$ID" > "$WORK_MD"
283
396
  jira issue view "$ID" --raw > "$METADATA_JSON"
284
397
  elif command -v acli >/dev/null 2>&1; then
285
- acli jira workitem view "$ID" > "$WORK_MD"
286
- echo '{}' > "$METADATA_JSON"
398
+ if ! acli jira workitem view "$ID" --json > "$METADATA_JSON" 2>/dev/null; then
399
+ echo '{}' > "$METADATA_JSON"
400
+ json_upsert "$METADATA_JSON" "title=Jira $ID"
401
+ fi
287
402
  else
288
403
  echo "ERROR: jira or acli CLI is required for jira tasks."
289
404
  exit 1
@@ -291,8 +406,8 @@ create_task() {
291
406
  ;;
292
407
  local)
293
408
  echo "Initializing local task workspace: $ID..."
294
- echo "# WORK: Local Task $ID" > "$WORK_MD"
295
409
  echo '{}' > "$METADATA_JSON"
410
+ json_upsert "$METADATA_JSON" "title=Local Task $ID"
296
411
  ;;
297
412
  *)
298
413
  echo "Unknown source: $SOURCE"
@@ -300,11 +415,8 @@ create_task() {
300
415
  ;;
301
416
  esac
302
417
 
303
- set_metadata_status_phase "active" "triaged"
304
- ensure_section "BRIEF" "- "
305
- ensure_section "GRILL" "- "
306
- ensure_section "PLAN" "- [ ] "
307
- ensure_section "LOG" ""
418
+ set_metadata_status_phase "active"
419
+ write_work_snapshot
308
420
  update_work_meta
309
421
  append_log "Task initialized via /triage"
310
422
  write_active_pointer
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
3
  # validate_active_task.sh
4
- # Lightweight, print-only validation of .workflow/active_task.json and related files.
4
+ # Lightweight, print-only validation of .workflow/active.json and compatibility .workflow/active_task.json.
5
5
  # This script never mutates task state; it only reports inconsistencies and suggested fixes.
6
6
 
7
7
  set -euo pipefail
@@ -9,6 +9,7 @@ set -euo pipefail
9
9
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
10
  TRIAGE_HELPER="$SCRIPT_DIR/triage_helper.sh"
11
11
  REPO_ROOT=$(git rev-parse --show-toplevel)
12
+ ACTIVE_JSON="$REPO_ROOT/.workflow/active.json"
12
13
  ATF="$REPO_ROOT/.workflow/active_task.json"
13
14
  CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || true)
14
15
  WORK_MD=""
@@ -152,29 +153,52 @@ print_json_field_report() {
152
153
  done
153
154
  }
154
155
 
155
- if [[ ! -f "$ATF" ]]; then
156
- error "No active_task.json found at $ATF"
156
+ POINTER_LABEL=""
157
+ TASK_PATH=""
158
+ TASK_SOURCE=""
159
+ TASK_ID=""
160
+ ACTIVE_TASK=""
161
+ POINTER_BRANCH=""
162
+
163
+ if [[ -f "$ACTIVE_JSON" ]]; then
164
+ if ! python3 -m json.tool "$ACTIVE_JSON" >/dev/null 2>&1; then
165
+ error "active.json is not valid JSON: $ACTIVE_JSON"
166
+ exit 1
167
+ fi
168
+ POINTER_LABEL="active.json"
169
+ echo "Active workflow file: $ACTIVE_JSON"
170
+ json_pretty "$ACTIVE_JSON" || true
171
+
172
+ TASK_PATH=$(json_read "$ACTIVE_JSON" '.taskPath // .path')
173
+ TASK_SOURCE=$(json_read "$ACTIVE_JSON" '.source')
174
+ TASK_ID=$(json_read "$ACTIVE_JSON" '.sourceId // .id')
175
+ ACTIVE_TASK=$(json_read "$ACTIVE_JSON" '.taskId // .id')
176
+ POINTER_BRANCH=$(json_read "$ACTIVE_JSON" '.branch')
177
+
178
+ print_json_field_report "active.json" "$ACTIVE_JSON" workflow id taskId source sourceId taskPath path branch stateFile
179
+ elif [[ -f "$ATF" ]]; then
180
+ if ! python3 -m json.tool "$ATF" >/dev/null 2>&1; then
181
+ error "active_task.json is not valid JSON: $ATF"
182
+ exit 1
183
+ fi
184
+ POINTER_LABEL="active_task.json"
185
+ echo "Legacy active task file: $ATF"
186
+ json_pretty "$ATF" || true
187
+
188
+ TASK_PATH=$(json_read "$ATF" '.taskPath // .path')
189
+ TASK_SOURCE=$(json_read "$ATF" '.source')
190
+ TASK_ID=$(json_read "$ATF" '.sourceId // .id')
191
+ ACTIVE_TASK=$(json_read "$ATF" '.active_task')
192
+ POINTER_BRANCH=$(json_read "$ATF" '.branch')
193
+
194
+ print_json_field_report "active_task.json" "$ATF" active_task source id sourceId taskPath path branch
195
+ else
196
+ error "No active workflow pointer found at $ACTIVE_JSON or $ATF"
157
197
  echo "Suggested fixes (dry):"
158
198
  echo " - Run: $TRIAGE_HELPER <source> <id>"
159
199
  exit 1
160
200
  fi
161
201
 
162
- if ! python3 -m json.tool "$ATF" >/dev/null 2>&1; then
163
- error "active_task.json is not valid JSON: $ATF"
164
- exit 1
165
- fi
166
-
167
- echo "Active task file: $ATF"
168
- json_pretty "$ATF" || true
169
-
170
- TASK_PATH=$(json_read "$ATF" '.taskPath // .path')
171
- TASK_SOURCE=$(json_read "$ATF" '.source')
172
- TASK_ID=$(json_read "$ATF" '.sourceId // .id')
173
- ACTIVE_TASK=$(json_read "$ATF" '.active_task')
174
- POINTER_BRANCH=$(json_read "$ATF" '.branch')
175
-
176
- print_json_field_report "active_task.json" "$ATF" active_task source id sourceId taskPath path branch
177
-
178
202
  echo
179
203
  if [[ -n "$TASK_PATH" ]]; then
180
204
  if [[ "$TASK_PATH" = /* ]]; then
@@ -184,7 +208,7 @@ if [[ -n "$TASK_PATH" ]]; then
184
208
  fi
185
209
  echo "Using taskPath -> checking: $WORK_MD"
186
210
  elif [[ -n "$TASK_ID" ]]; then
187
- echo "No taskPath in active_task.json; attempting to derive from source+id"
211
+ echo "No taskPath in $POINTER_LABEL; attempting to derive from source+id"
188
212
  if [[ -n "$TASK_SOURCE" ]]; then
189
213
  TASK_FOLDER="$TASK_SOURCE-$TASK_ID"
190
214
  else
@@ -193,7 +217,7 @@ elif [[ -n "$TASK_ID" ]]; then
193
217
  WORK_MD="$REPO_ROOT/.workflow/tasks/$TASK_FOLDER/WORK.md"
194
218
  echo "Derived path: $WORK_MD"
195
219
  else
196
- error "Neither taskPath/path nor sourceId/id present in active_task.json"
220
+ error "Neither taskPath/path nor sourceId/id present in $POINTER_LABEL"
197
221
  fi
198
222
 
199
223
  if [[ -n "$WORK_MD" && -f "$WORK_MD" ]]; then
@@ -209,7 +233,7 @@ if [[ -n "$ACTIVE_TASK" && -n "$TASK_DIR" ]]; then
209
233
  if [[ "$ACTIVE_TASK" == "$ACTUAL_TASK_FOLDER" ]]; then
210
234
  ok "active_task matches task folder ($ACTIVE_TASK)"
211
235
  else
212
- warn "active_task ($ACTIVE_TASK) differs from task folder ($ACTUAL_TASK_FOLDER)"
236
+ warn "pointer task id ($ACTIVE_TASK) differs from task folder ($ACTUAL_TASK_FOLDER)"
213
237
  fi
214
238
  fi
215
239
 
@@ -225,8 +249,8 @@ META_ID=""
225
249
  META_SOURCE=""
226
250
  META_TASK_FOLDER=""
227
251
  META_STATUS=""
228
- META_PHASE=""
229
252
  META_BRANCH=""
253
+ DERIVED_PHASE=""
230
254
 
231
255
  if [[ -n "$META" ]]; then
232
256
  if [[ -f "$META" ]]; then
@@ -234,20 +258,19 @@ if [[ -n "$META" ]]; then
234
258
  echo
235
259
  echo "Metadata file: $META"
236
260
  json_pretty "$META" || true
237
- print_json_field_report "metadata.json" "$META" id source taskFolder status phase branch createdAt updatedAt
261
+ print_json_field_report "metadata.json" "$META" id source taskFolder status branch createdAt updatedAt
238
262
 
239
263
  META_ID=$(json_read "$META" '.id')
240
264
  META_SOURCE=$(json_read "$META" '.source')
241
265
  META_TASK_FOLDER=$(json_read "$META" '.taskFolder')
242
266
  META_STATUS=$(json_read "$META" '.status')
243
- META_PHASE=$(json_read "$META" '.phase')
244
267
  META_BRANCH=$(json_read "$META" '.branch')
245
268
 
246
269
  if [[ -n "$META_ID" && -n "$TASK_ID" && "$META_ID" != "$TASK_ID" ]]; then
247
- warn "metadata.id ($META_ID) differs from active_task id/sourceId ($TASK_ID)"
270
+ warn "metadata.id ($META_ID) differs from pointer id/sourceId ($TASK_ID)"
248
271
  fi
249
272
  if [[ -n "$META_SOURCE" && -n "$TASK_SOURCE" && "$META_SOURCE" != "$TASK_SOURCE" ]]; then
250
- warn "metadata.source ($META_SOURCE) differs from active_task.source ($TASK_SOURCE)"
273
+ warn "metadata.source ($META_SOURCE) differs from pointer source ($TASK_SOURCE)"
251
274
  fi
252
275
  if [[ -n "$META_TASK_FOLDER" && -n "$TASK_DIR" && "$META_TASK_FOLDER" != "$(basename "$TASK_DIR")" ]]; then
253
276
  warn "metadata.taskFolder ($META_TASK_FOLDER) differs from task folder ($(basename "$TASK_DIR"))"
@@ -275,16 +298,19 @@ if [[ -n "$WORK_MD" && -f "$WORK_MD" ]]; then
275
298
  esac
276
299
  done
277
300
 
278
- if [[ "$META_PHASE" =~ ^(planned|implementing|verifying|synced|closed)$ ]]; then
279
- if [[ "$(has_plan_checkbox "$WORK_MD")" == "yes" ]]; then
280
- ok "[PLAN] contains checkboxes for phase=$META_PHASE"
281
- else
282
- warn "phase=$META_PHASE but [PLAN] has no checkboxes"
283
- fi
301
+ if [[ "$(section_body_nonempty "PLAN" "$WORK_MD")" == "yes" ]]; then
302
+ DERIVED_PHASE="planned"
303
+ elif [[ "$(section_body_nonempty "GRILL" "$WORK_MD")" == "yes" ]]; then
304
+ DERIVED_PHASE="grilled"
305
+ elif [[ "$(section_body_nonempty "BRIEF" "$WORK_MD")" == "yes" ]]; then
306
+ DERIVED_PHASE="framed"
307
+ else
308
+ DERIVED_PHASE="triaged"
284
309
  fi
310
+ ok "derived phase from WORK.md: $DERIVED_PHASE"
285
311
 
286
- if [[ "$META_PHASE" == "triaged" && "$(section_body_nonempty "BRIEF" "$WORK_MD")" == "yes" ]]; then
287
- warn "phase=triaged but [BRIEF] appears populated; consider updating phase metadata"
312
+ if [[ "$DERIVED_PHASE" == "planned" && "$(has_plan_checkbox "$WORK_MD")" != "yes" ]]; then
313
+ warn "derived phase=planned but [PLAN] has no checkboxes"
288
314
  fi
289
315
  fi
290
316
 
@@ -296,12 +322,12 @@ echo
296
322
  echo "Suggested fixes (dry):"
297
323
  if [[ -n "$WORK_MD" && ! -f "$WORK_MD" ]]; then
298
324
  echo " - Re-run triage: $TRIAGE_HELPER <source> <id>"
299
- echo " - Or update .workflow/active_task.json to include a correct taskPath or source+id"
325
+ echo " - Or update the active workflow pointer to include a correct taskPath or source+id"
300
326
  fi
301
327
  if [[ -n "$META" && ! -f "$META" ]]; then
302
328
  echo " - Resume/backfill metadata: $TRIAGE_HELPER ${TASK_SOURCE:-local} ${TASK_ID:-task} resume"
303
329
  elif [[ -n "$META" && -f "$META" ]]; then
304
- if [[ -z "$META_STATUS" || -z "$META_PHASE" || -z "$(json_read "$META" '.createdAt')" || -z "$(json_read "$META" '.updatedAt')" ]]; then
330
+ if [[ -z "$META_STATUS" || -z "$(json_read "$META" '.createdAt')" || -z "$(json_read "$META" '.updatedAt')" ]]; then
305
331
  echo " - Backfill missing metadata fields: $TRIAGE_HELPER ${TASK_SOURCE:-local} ${TASK_ID:-task} resume"
306
332
  fi
307
333
  fi
package/sync/SKILL.md CHANGED
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  name: sync
3
+ workflow: rpiv
4
+ workflowPhase: sync
3
5
  description: Synchronizes local RPIV task state (WORK.md) to external trackers (Jira, GitHub, GitLab). Use this to publish progress, update implementation status, and maintain a durable audit trail between local development and remote project management tools.
4
6
  ---
5
7
 
@@ -8,11 +10,13 @@ description: Synchronizes local RPIV task state (WORK.md) to external trackers (
8
10
  Maintains consistency between local `.workflow` state and the remote source of truth using a single Pi-owned living status comment per task.
9
11
 
10
12
  ## Guardrails
11
- - **Pre-flight**: Always read `.workflow/active_task.json` and the active `WORK.md` before executing.
13
+ - **Pre-flight**: Always read `.workflow/active.json` first, then compatibility `.workflow/active_task.json` only if needed, and the active `WORK.md` before executing.
12
14
  - **Privacy**: NEVER sync secrets, environment variables, or private notes not intended for stakeholders.
13
15
  - **Integrity**: Do not modify `[BRIEF]` or `[GRILL]` sections.
14
16
  - **Idempotency**: If the remote Pi status already reflects the current local state, do not post or update.
15
17
  - **Human safety**: NEVER edit human-authored comments. Only update comments/notes containing the Pi sync marker.
18
+ - **Target ownership**: Sync the executable child issue/MR/PR that the work completed, not the umbrella parent, unless the user explicitly asks for parent status. If the active GitHub issue has sub-issues, verify the PR/body/current request points to the right child before posting.
19
+ - **Shell safety**: Never pass markdown bodies inline through shell strings. Write bodies to files and use `--body-file` or JSON `--input` API calls so backticks and `$()` cannot execute.
16
20
 
17
21
  ## Living status marker
18
22
  Every sync body MUST include this marker at the end:
@@ -44,7 +48,12 @@ Do **not** use latest-comment ownership as the primary decision. Latest-comment-
44
48
  ## Workflow
45
49
 
46
50
  ### 1. Discovery & State Loading
47
- - Identify the platform and ID from `.workflow/active_task.json`.
51
+ - Identify the platform and ID from `.workflow/active.json`, falling back to compatibility `.workflow/active_task.json` only when required.
52
+ - For GitHub, check hierarchy before syncing umbrella issues:
53
+ ```bash
54
+ gh issue view <id> --json parent,subIssues --jq '{parent:.parent, subIssues:.subIssues}'
55
+ ```
56
+ If the issue has sub-issues and the work maps to one child, switch the sync target to that child or ask once. Do not sync a child deliverable to the parent just because the active task points at the parent.
48
57
  - Extract **Slices** from `[PLAN]`, **Status** from `[LOG]`, and **Artifacts** such as PR/MR links, commit hashes, and verification output.
49
58
 
50
59
  ### 2. Payload Preparation
@@ -79,8 +88,9 @@ gh api repos/:owner/:repo/issues/<id>/comments --paginate \
79
88
 
80
89
  Update:
81
90
  ```bash
91
+ jq -n --rawfile body body.md '{body: $body}' > body.json
82
92
  gh api -X PATCH repos/:owner/:repo/issues/comments/<comment_id> \
83
- -f body=@body.md
93
+ --input body.json
84
94
  ```
85
95
 
86
96
  Create:
@@ -92,6 +102,7 @@ Rules:
92
102
  - update only a comment containing the marker
93
103
  - no-op when normalized body is already current
94
104
  - create only when no marker comment exists
105
+ - keep markdown in files; do not use inline `-f body="$(cat body.md)"` or shell-expanded PR/comment bodies
95
106
 
96
107
  #### GitLab Issues / MRs
97
108
  Use notes API for issues or merge requests.
package/triage/SKILL.md CHANGED
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  name: triage
3
+ workflow: rpiv
4
+ workflowPhase: triage
3
5
  description: "Ingest or resume a tracked/local task in the RPIV workspace. Use when starting or returning to jira:, github:, gitlab:, or local: work and you need canonical WORK.md state without duplicating the scaffold."
4
6
  ---
5
7
 
@@ -8,10 +10,10 @@ description: "Ingest or resume a tracked/local task in the RPIV workspace. Use w
8
10
  Start RPIV by creating, resuming, or explicitly reopening a task workspace.
9
11
 
10
12
  ## Guardrails
11
- - READ: user argument, `.workflow/active_task.json` if present, target `metadata.json`, and target `WORK.md` if resuming.
12
- - WRITE: `.workflow/tasks/[source-id]/WORK.md`, `.workflow/tasks/[source-id]/metadata.json`, `.workflow/active_task.json`; optional `.reposcry/` cache files only if RepoScry is installed.
13
+ - READ: user argument, `.workflow/active.json` first and compatibility `.workflow/active_task.json` only if present/needed, target `metadata.json`, and target `WORK.md` if resuming.
14
+ - WRITE: `.workflow/tasks/[source-id]/WORK.md`, `.workflow/tasks/[source-id]/metadata.json`, `.workflow/active.json`.
13
15
  - On **create**, initialize required guarded sections only if absent: `[BRIEF]`, `[GRILL]`, `[PLAN]`, `[LOG]`, `[META]`.
14
- - On **resume**, only update `.workflow/active_task.json`, metadata timestamps/state as needed, and `[META]`, then append one concise `[LOG]` entry.
16
+ - On **resume**, only update `.workflow/active.json`, metadata timestamps/state as needed, and `[META]`, then append one concise `[LOG]` entry.
15
17
  - NEVER: duplicate guarded sections.
16
18
  - NEVER: overwrite existing `[BRIEF]`, `[GRILL]`, or `[PLAN]` during triage.
17
19
  - NEVER: create `PROBLEM.md`, `PRD.md`, `PLAN.md`, or `EVIDENCE.md`.
@@ -58,17 +60,14 @@ Start RPIV by creating, resuming, or explicitly reopening a task workspace.
58
60
  - Preferred absolute path: `<skill_dir>/../scripts/triage_helper.sh`.
59
61
  3. Read the resulting active pointer, metadata, and `WORK.md`.
60
62
  4. Determine action based on helper output and task state: `created`, `resumed`, `reopened`, `refused`, `fresh/reset`.
61
- 5. Optional RepoScry bootstrap: if bundled `../scripts/reposcry-bootstrap.sh` available, run it to seed `.reposcry/` for later `/frame` and `/grill-with-docs`.
62
- - Run `../scripts/reposcry-bootstrap.sh --pulse` to determine "Repo Pulse" (Warm/Cold/Missing) for the output contract.
63
- - Run `../scripts/reposcry-bootstrap.sh` to ensure the local cache is initialized.
64
- - The helper must ensure `.reposcry/` is ignored, must stop if `.reposcry/` tracked/staged, and continue normally if RepoScry is unavailable.
65
- 6. Verify branch: ensure current branch matches metadata `branch` (or is `main`/`master` for planning).
66
- 7. If task was newly created, infer classification:
63
+ 6. New task creation should store structured tracker facts in `metadata.json` and write a concise normalized intake snapshot into `WORK.md`; never paste raw tracker CLI rendering.
64
+ 7. Verify branch: ensure current branch matches metadata `branch` (or is `main`/`master` for planning).
65
+ 8. If task was newly created, infer classification:
67
66
  - `github:`, `gitlab:`, `jira:` labels/type often indicate Problem (bug) or Proposal (feature).
68
67
  - Local tasks default to Proposal unless specified.
69
- 8. If resuming, check `[BRIEF]` and `[PLAN]` completion status.
70
- 9. **Final Guidance**: record current branch in `[META]`. For `jira:` tasks, also preserve the Jira key in `[META]` and mention `/implement` must use it in the commit subject. Planning on `main`/`master` allowed; implementation will use `/implement` branch enforcement.
71
- 10. End by recommending next valid command:
68
+ 9. If resuming, check `[BRIEF]` and `[PLAN]` completion status.
69
+ 10. **Final Guidance**: record current branch in `[META]`. For `jira:` tasks, preserve the Jira key in `[META]`. Planning on `main`/`master` allowed; implementation will use `/implement` branch enforcement.
70
+ 11. End by recommending next valid command:
72
71
  - newly created -> `/frame`
73
72
  - existing empty `[BRIEF]` -> `/frame`
74
73
  - framed but not grilled -> `/grill-with-docs`
@@ -83,6 +82,5 @@ End with:
83
82
  - **Status**: active / blocked / done / archived / unknown
84
83
  - **Phase**: triaged / framed / grilled / planned / implementing / verifying / synced / closed / unknown
85
84
  - **Branch**: current branch
86
- - **Repo Pulse**: Warm / Cold / Missing / Not applicable
87
85
  - **Classification**: Problem / Proposal
88
86
  - **Next step**: `/frame`, `/grill-with-docs`, `/plan`, `/implement`, or explicit reopen/fresh choice
package/verify/SKILL.md CHANGED
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  name: verify
3
+ workflow: rpiv
4
+ workflowPhase: verify
3
5
  description: Verify the active slice or task against WORK.md, quality gates, and review readiness. Use after implementation or manual changes to decide whether work is ready for sync, review, or post-merge-prune.
4
6
  ---
5
7
 
@@ -8,7 +10,7 @@ description: Verify the active slice or task against WORK.md, quality gates, and
8
10
  The final gate for a slice or task. Verify truth before reporting progress.
9
11
 
10
12
  ## Guardrails
11
- - READ: `.workflow/active_task.json`, active `WORK.md` `[BRIEF]`, `[PLAN]`, and `[LOG]`.
13
+ - READ: `.workflow/active.json` first, then compatibility `.workflow/active_task.json` only if needed, plus active `WORK.md` `[BRIEF]`, `[PLAN]`, and `[LOG]`.
12
14
  - WRITE: `WORK.md` -> `[PLAN]` checkboxes and append to `[LOG]` only.
13
15
  - NEVER: add `Signed-off-by`; tell the human to sign if needed.
14
16
  - NEVER: transition tracker state if verification fails.
@@ -17,9 +19,8 @@ The final gate for a slice or task. Verify truth before reporting progress.
17
19
  ## Workflow
18
20
  1. Compare code changes against `[BRIEF]` and the current `[PLAN]` slice.
19
21
  2. Run stated verification commands and available quality gates.
20
- 3. If RepoScry is available, optionally add graph-aware evidence with commands such as `reposcry validate main HEAD` and `reposcry --repo . get_affected_flows main HEAD`. Treat RepoScry as supplemental evidence, not a hard requirement. Verify `.reposcry/` is not staged or tracked before reporting review readiness.
21
22
  4. Check for AI artifacts: placeholder comments, fake APIs, dead code, inconsistent naming.
22
- 5. Confirm commit messages include Conventional Commit format. When AI contributed, you MUST append an `Assisted-by` trailer. Do NOT guess or hallucinate the model name from your system prompt. You must run `bash ~/RQZ/personal/nothing/packages/norpiv/scripts/get-pi-model.sh` and use its exact output to construct the trailer: `Assisted-by: <EXACT_OUTPUT> [tools]`.
23
+ 5. Confirm commits and PR/MR metadata satisfy the repository's own hooks, CI, and release policies. Do not invent or enforce extra RPIV-specific formatting here.
23
24
  6. If passing, mark the slice checkbox complete in `[PLAN]` and append verification evidence to `[LOG]` (Format: `YYYY-MM-DD hh:mm AM/PM`).
24
25
  7. Recommend `/sync` for tracker update, or `/post-merge-prune` if the task is fully merged and user approves.
25
26
 
@@ -1,55 +0,0 @@
1
- ---
2
- name: post-merge-prune
3
- description: "Post-merge branch cleanup only. Use after a PR/MR is merged and the local feature branch is stale: prune remote refs and delete local branches already merged into the target branch."
4
- ---
5
-
6
- # Skill: post-merge-prune
7
-
8
- > [!IMPORTANT]
9
- > **LLM INSTRUCTION**: This skill is a deliberate post-merge deletion tool. If the user invokes `post-merge-prune`, they want stale local branches removed after merge into the intended target branch. Do not offer a menu of options. Do not ask "Dry run or Execute?". Proceed directly.
10
-
11
- Use this skill after a PR/MR has been merged into its intended branch, typically `main`, to remove stale remote-tracking refs and delete the matching local branches because the work is done.
12
-
13
- ## Goal
14
- - Switch to the intended target branch (`main` by default).
15
- - Prune deleted remote branches.
16
- - Delete local branches that are already merged into the target branch.
17
- - Optionally remove matching workflow task folders.
18
-
19
- ## Workflow
20
-
21
- ### Step 1: Go to the target branch
22
- - `git checkout main` (or the intended merge target)
23
- - If the tree is dirty, stash first.
24
-
25
- ### Step 2: Prune remote-tracking refs
26
- - `git remote update origin --prune`
27
- - This removes local refs for remote branches that were deleted after merge.
28
-
29
- ### Step 3: Delete merged local branches
30
- Iterate through local branches and delete the ones already merged into the target branch:
31
- - Use `git branch -d <branch>` for normal merges.
32
- - If `-d` refuses but `git log main..[branch]` is empty, the branch was squash-merged; use `git branch -D <branch>`.
33
- - Never delete the active target branch.
34
-
35
- ### Step 4: Optional workflow cleanup
36
- If a deleted branch has a matching `.workflow/tasks/*` folder, remove it.
37
- If `.workflow/active_task.json` points to a deleted task, clear it.
38
-
39
- ### Step 5: Verify
40
- - `git branch -a` should no longer show deleted refs.
41
- - `git branch --merged main` should only show branches you intend to keep.
42
- - `.workflow/tasks/` should not contain deleted tasks.
43
-
44
- ## Guardrails
45
- - **RESOLUTION OVER REPORTING**: If merge status is unclear, check `git branch --merged main` or `git log main..branch` and resolve it.
46
- - **NO DRY RUNS BY DEFAULT**: Execute the cleanup unless the user explicitly asks for a preview.
47
- - **SMART DELETE**: If a branch is squash-merged, delete it with `git branch -D` after confirming there are no unique commits left.
48
- - **ACTIVE BRANCH PROTECTION**: Do not delete the current branch.
49
- - **UNMERGED WORK**: If a branch has unique commits and no remote, ask once before force-deleting.
50
-
51
- ## Output Contract
52
- Return a concise report:
53
- - **Cleaned**: deleted branches / task folders
54
- - **Kept**: branches still in use
55
- - **Working Branch**: the branch left checked out (should be `main`)