@raquezha/norpiv 0.0.7 → 0.1.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.
@@ -120,21 +120,12 @@ PY
120
120
 
121
121
  write_active_pointer() {
122
122
  mkdir -p ".workflow"
123
- python3 - ".workflow/active_task.json" ".workflow/active.json" "$TASK_FOLDER" "$SOURCE" "$ID" "$TASK_DIR" "$WORK_MD" "$BRANCH_NAME" "$ISO_NOW" <<'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
- active_task_path, active_workflow_path, active_task, source, raw_id, task_path, state_file, branch, now = sys.argv[1:]
129
- legacy_data = {
130
- "active_task": active_task,
131
- "source": source,
132
- "id": raw_id,
133
- "sourceId": raw_id,
134
- "taskPath": task_path,
135
- "path": task_path,
136
- "branch": branch,
137
- }
128
+ active_workflow_path, active_task, source, raw_id, task_path, state_file, branch, now = sys.argv[1:]
138
129
  workflow_data = {
139
130
  "workflow": "rpiv",
140
131
  "id": active_task,
@@ -147,9 +138,7 @@ workflow_data = {
147
138
  "branch": branch,
148
139
  "startedAt": now,
149
140
  "updatedAt": now,
150
- "compatPointer": ".workflow/active_task.json",
151
141
  }
152
- Path(active_task_path).write_text(json.dumps(legacy_data, indent=2) + "\n")
153
142
  Path(active_workflow_path).write_text(json.dumps(workflow_data, indent=2) + "\n")
154
143
  PY
155
144
  }
@@ -202,24 +191,43 @@ PY
202
191
  }
203
192
 
204
193
  update_work_meta() {
205
- local status phase
194
+ local status
206
195
  status=$(json_get "$METADATA_JSON" status)
207
- phase=$(json_get "$METADATA_JSON" phase)
208
196
  status=${status:-active}
209
- phase=${phase:-triaged}
210
197
 
211
- python3 - "$WORK_MD" "$BRANCH_NAME" "$status" "$phase" "$SOURCE" "$ID" <<'PY'
198
+ python3 - "$WORK_MD" "$BRANCH_NAME" "$status" "$SOURCE" "$ID" <<'PY'
212
199
  import re
213
200
  import sys
214
201
  from pathlib import Path
215
202
 
216
203
  path = Path(sys.argv[1])
217
- branch, status, phase, source, raw_id = sys.argv[2:]
204
+ branch, status, source, raw_id = sys.argv[2:]
218
205
  text = path.read_text() if path.exists() else ""
219
206
  lines = text.splitlines()
220
207
  header_re = re.compile(r"^(## )?\[[A-Z0-9_-]+\]\s*$")
221
208
  meta_re = re.compile(r"^(## )?\[META\]\s*$")
222
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
+
223
231
  new_block = [
224
232
  "## [META]",
225
233
  f"- Branch: `{branch}`",
@@ -247,6 +255,100 @@ path.write_text("\n".join(updated).rstrip() + "\n")
247
255
  PY
248
256
  }
249
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
+
250
352
  backfill_metadata() {
251
353
  json_upsert "$METADATA_JSON" \
252
354
  "id=$ID" \
@@ -254,51 +356,49 @@ backfill_metadata() {
254
356
  "branch=$BRANCH_NAME" \
255
357
  "taskFolder=$TASK_FOLDER" \
256
358
  "?status=active" \
257
- "?phase=triaged" \
258
359
  "createdAt=$ISO_NOW" \
259
360
  "updatedAt=$ISO_NOW"
260
361
  }
261
362
 
262
363
  set_metadata_status_phase() {
263
364
  local status="$1"
264
- local phase="$2"
265
365
  json_upsert "$METADATA_JSON" \
266
366
  "id=$ID" \
267
367
  "source=$SOURCE" \
268
368
  "branch=$BRANCH_NAME" \
269
369
  "taskFolder=$TASK_FOLDER" \
270
370
  "status=$status" \
271
- "phase=$phase" \
272
371
  "createdAt=$ISO_NOW" \
273
372
  "updatedAt=$ISO_NOW"
274
373
  }
275
374
 
276
375
  create_task() {
277
- mkdir -p "$TASK_DIR"
376
+ mkdir -p "$TASK_DIR/evidence"
278
377
  echo "Creating task workspace in $TASK_DIR..."
279
378
 
280
379
  case "$SOURCE" in
281
380
  github)
282
381
  command -v gh >/dev/null 2>&1 || { echo "ERROR: gh CLI is required for github tasks."; exit 1; }
283
382
  echo "Fetching GitHub Issue #$ID..."
284
- gh issue view "$ID" --json title,body,author,labels,comments > "$METADATA_JSON"
285
- echo "# WORK: GitHub #$ID" > "$WORK_MD"
286
- gh issue view "$ID" >> "$WORK_MD"
383
+ gh issue view "$ID" --json title,body,author,labels,url,number,createdAt,updatedAt > "$METADATA_JSON"
287
384
  ;;
288
385
  gitlab)
289
386
  command -v glab >/dev/null 2>&1 || { echo "ERROR: glab CLI is required for gitlab tasks."; exit 1; }
290
387
  echo "Fetching GitLab Issue #$ID..."
291
- glab issue view "$ID" > "$WORK_MD"
292
- 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
293
392
  ;;
294
393
  jira)
295
394
  echo "Fetching Jira Ticket $ID..."
296
395
  if command -v jira >/dev/null 2>&1; then
297
- jira issue view "$ID" > "$WORK_MD"
298
396
  jira issue view "$ID" --raw > "$METADATA_JSON"
299
397
  elif command -v acli >/dev/null 2>&1; then
300
- acli jira workitem view "$ID" > "$WORK_MD"
301
- 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
302
402
  else
303
403
  echo "ERROR: jira or acli CLI is required for jira tasks."
304
404
  exit 1
@@ -306,8 +406,8 @@ create_task() {
306
406
  ;;
307
407
  local)
308
408
  echo "Initializing local task workspace: $ID..."
309
- echo "# WORK: Local Task $ID" > "$WORK_MD"
310
409
  echo '{}' > "$METADATA_JSON"
410
+ json_upsert "$METADATA_JSON" "title=Local Task $ID"
311
411
  ;;
312
412
  *)
313
413
  echo "Unknown source: $SOURCE"
@@ -315,11 +415,8 @@ create_task() {
315
415
  ;;
316
416
  esac
317
417
 
318
- set_metadata_status_phase "active" "triaged"
319
- ensure_section "BRIEF" "- "
320
- ensure_section "GRILL" "- "
321
- ensure_section "PLAN" "- [ ] "
322
- ensure_section "LOG" ""
418
+ set_metadata_status_phase "active"
419
+ write_work_snapshot
323
420
  update_work_meta
324
421
  append_log "Task initialized via /triage"
325
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
@@ -10,11 +10,13 @@ description: Synchronizes local RPIV task state (WORK.md) to external trackers (
10
10
  Maintains consistency between local `.workflow` state and the remote source of truth using a single Pi-owned living status comment per task.
11
11
 
12
12
  ## Guardrails
13
- - **Pre-flight**: Always read `.workflow/active.json` / `.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.
14
14
  - **Privacy**: NEVER sync secrets, environment variables, or private notes not intended for stakeholders.
15
15
  - **Integrity**: Do not modify `[BRIEF]` or `[GRILL]` sections.
16
16
  - **Idempotency**: If the remote Pi status already reflects the current local state, do not post or update.
17
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.
18
20
 
19
21
  ## Living status marker
20
22
  Every sync body MUST include this marker at the end:
@@ -46,7 +48,12 @@ Do **not** use latest-comment ownership as the primary decision. Latest-comment-
46
48
  ## Workflow
47
49
 
48
50
  ### 1. Discovery & State Loading
49
- - Identify the platform and ID from `.workflow/active.json` or compatibility `.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.
50
57
  - Extract **Slices** from `[PLAN]`, **Status** from `[LOG]`, and **Artifacts** such as PR/MR links, commit hashes, and verification output.
51
58
 
52
59
  ### 2. Payload Preparation
@@ -81,8 +88,9 @@ gh api repos/:owner/:repo/issues/<id>/comments --paginate \
81
88
 
82
89
  Update:
83
90
  ```bash
91
+ jq -n --rawfile body body.md '{body: $body}' > body.json
84
92
  gh api -X PATCH repos/:owner/:repo/issues/comments/<comment_id> \
85
- -f body=@body.md
93
+ --input body.json
86
94
  ```
87
95
 
88
96
  Create:
@@ -94,6 +102,7 @@ Rules:
94
102
  - update only a comment containing the marker
95
103
  - no-op when normalized body is already current
96
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
97
106
 
98
107
  #### GitLab Issues / MRs
99
108
  Use notes API for issues or merge requests.
package/triage/SKILL.md CHANGED
@@ -10,10 +10,10 @@ description: "Ingest or resume a tracked/local task in the RPIV workspace. Use w
10
10
  Start RPIV by creating, resuming, or explicitly reopening a task workspace.
11
11
 
12
12
  ## Guardrails
13
- - READ: user argument, `.workflow/active.json` / `.workflow/active_task.json` if present, 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`, `.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`.
15
15
  - On **create**, initialize required guarded sections only if absent: `[BRIEF]`, `[GRILL]`, `[PLAN]`, `[LOG]`, `[META]`.
16
- - On **resume**, only update `.workflow/active.json`, `.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.
17
17
  - NEVER: duplicate guarded sections.
18
18
  - NEVER: overwrite existing `[BRIEF]`, `[GRILL]`, or `[PLAN]` during triage.
19
19
  - NEVER: create `PROBLEM.md`, `PRD.md`, `PLAN.md`, or `EVIDENCE.md`.
@@ -21,6 +21,7 @@ Start RPIV by creating, resuming, or explicitly reopening a task workspace.
21
21
  - NEVER: guess source from `#123`; require explicit `jira:`, `github:`, `gitlab:`, or `local:`.
22
22
  - NEVER: strip or hide the Jira key for `jira:` tasks; preserve it in `[META]` and in the triage log so `/implement` can require it in the commit subject.
23
23
  - NEVER: mutate `done` or `archived` tasks unless the user explicitly requested `reopen`, `fresh`, or `reset`.
24
+ - TASK EVIDENCE: Store task-acquired evidence (screenshots, attachments, PDFs, specs) under `.workflow/tasks/[source-id]/evidence/`.
24
25
 
25
26
  ## Command forms
26
27
 
@@ -60,17 +61,14 @@ Start RPIV by creating, resuming, or explicitly reopening a task workspace.
60
61
  - Preferred absolute path: `<skill_dir>/../scripts/triage_helper.sh`.
61
62
  3. Read the resulting active pointer, metadata, and `WORK.md`.
62
63
  4. Determine action based on helper output and task state: `created`, `resumed`, `reopened`, `refused`, `fresh/reset`.
63
- 5. Optional RepoScry bootstrap: if bundled `../scripts/reposcry-bootstrap.sh` available, run it to seed `.reposcry/` for later `/frame` and `/grill-with-docs`.
64
- - Run `../scripts/reposcry-bootstrap.sh --pulse` to determine "Repo Pulse" (Warm/Cold/Missing) for the output contract.
65
- - Run `../scripts/reposcry-bootstrap.sh` to ensure the local cache is initialized.
66
- - The helper must ensure `.reposcry/` is ignored, must stop if `.reposcry/` tracked/staged, and continue normally if RepoScry is unavailable.
67
- 6. Verify branch: ensure current branch matches metadata `branch` (or is `main`/`master` for planning).
68
- 7. If task was newly created, infer classification:
64
+ 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.
65
+ 7. Verify branch: ensure current branch matches metadata `branch` (or is `main`/`master` for planning).
66
+ 8. If task was newly created, infer classification:
69
67
  - `github:`, `gitlab:`, `jira:` labels/type often indicate Problem (bug) or Proposal (feature).
70
68
  - Local tasks default to Proposal unless specified.
71
- 8. If resuming, check `[BRIEF]` and `[PLAN]` completion status.
72
- 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.
73
- 10. End by recommending next valid command:
69
+ 9. If resuming, check `[BRIEF]` and `[PLAN]` completion status.
70
+ 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.
71
+ 11. End by recommending next valid command:
74
72
  - newly created -> `/frame`
75
73
  - existing empty `[BRIEF]` -> `/frame`
76
74
  - framed but not grilled -> `/grill-with-docs`
@@ -85,6 +83,5 @@ End with:
85
83
  - **Status**: active / blocked / done / archived / unknown
86
84
  - **Phase**: triaged / framed / grilled / planned / implementing / verifying / synced / closed / unknown
87
85
  - **Branch**: current branch
88
- - **Repo Pulse**: Warm / Cold / Missing / Not applicable
89
86
  - **Classification**: Problem / Proposal
90
87
  - **Next step**: `/frame`, `/grill-with-docs`, `/plan`, `/implement`, or explicit reopen/fresh choice
package/verify/SKILL.md CHANGED
@@ -10,18 +10,18 @@ description: Verify the active slice or task against WORK.md, quality gates, and
10
10
  The final gate for a slice or task. Verify truth before reporting progress.
11
11
 
12
12
  ## Guardrails
13
- - READ: `.workflow/active.json` / `.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]`.
14
14
  - WRITE: `WORK.md` -> `[PLAN]` checkboxes and append to `[LOG]` only.
15
15
  - NEVER: add `Signed-off-by`; tell the human to sign if needed.
16
16
  - NEVER: transition tracker state if verification fails.
17
17
  - NEVER: delete `.workflow` task folders without explicit user approval.
18
+ - EVIDENCE ISOLATION: Resolve task evidence from active task workspace `.workflow/tasks/<task-id>/evidence/` or task state. Do not scan arbitrary repository files as task evidence.
18
19
 
19
20
  ## Workflow
20
21
  1. Compare code changes against `[BRIEF]` and the current `[PLAN]` slice.
21
22
  2. Run stated verification commands and available quality gates.
22
- 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.
23
23
  4. Check for AI artifacts: placeholder comments, fake APIs, dead code, inconsistent naming.
24
- 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]`.
24
+ 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.
25
25
  6. If passing, mark the slice checkbox complete in `[PLAN]` and append verification evidence to `[LOG]` (Format: `YYYY-MM-DD hh:mm AM/PM`).
26
26
  7. Recommend `/sync` for tracker update, or `/post-merge-prune` if the task is fully merged and user approves.
27
27