ops-wiki-agent-kit 0.1.1 → 0.1.3

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.
Files changed (22) hide show
  1. package/.github/skills/source-code-to-ops-spec-guidelines/references/supporting-output-format-diagram-and-example-reference.md +2 -0
  2. package/package.json +1 -1
  3. package/.github/agents/docs-target-catalog.agent.md +0 -40
  4. package/.github/agents/docs-target-queue-from-catalog.agent.md +0 -35
  5. package/.github/agents/source-code-to-spec-reviewer.agent.md +0 -53
  6. package/.github/prompts/00-generate-target-all-spec.prompt.md +0 -35
  7. package/.github/prompts/04-review-target-spec.prompt.md +0 -24
  8. package/.github/prompts/docs-target-catalog.prompt.md +0 -30
  9. package/.github/prompts/docs-target-queue-from-catalog.prompt.md +0 -28
  10. package/.github/prompts/generate-docs-index.prompt.md +0 -77
  11. package/.github/skills/database-query/SKILL.md +0 -142
  12. package/.github/skills/database-query/references/client-commands.md +0 -197
  13. package/.github/skills/database-query/references/query-safety.md +0 -109
  14. package/.github/skills/database-query/scripts/find_db_config.py +0 -273
  15. package/.github/skills/docs-target-catalog/SKILL.md +0 -194
  16. package/.github/skills/docs-target-catalog/references/docs-target-queue-conversion.md +0 -164
  17. package/.github/skills/docs-target-catalog/references/entrypoint-source-patterns.md +0 -83
  18. package/.github/skills/docs-target-catalog/references/output-templates.md +0 -168
  19. package/.github/skills/docs-target-queue-from-catalog/SKILL.md +0 -85
  20. package/.github/skills/docs-target-queue-from-catalog/references/docs-target-queue-contract.md +0 -172
  21. package/.github/skills/docs-target-queue-from-catalog/references/metadata-acquisition-patterns.md +0 -244
  22. package/.github/skills/docs-target-queue-from-catalog/scripts/write_documentation_target_queue.py +0 -544
@@ -1,544 +0,0 @@
1
- #!/usr/bin/env python3
2
- import sys
3
-
4
- sys.dont_write_bytecode = True
5
-
6
- import argparse
7
- import csv
8
- import json
9
- import re
10
- from collections import OrderedDict, defaultdict
11
- from pathlib import Path
12
-
13
- for parent in Path(__file__).resolve().parents:
14
- shared_scripts = parent / "source-code-to-spec-tools" / "scripts"
15
- if (shared_scripts / "queue_contract.py").exists():
16
- sys.path.insert(0, str(shared_scripts))
17
- break
18
- else:
19
- raise SystemExit("Cannot locate source-code-to-spec-tools/scripts/queue_contract.py")
20
-
21
- from queue_contract import (
22
- BASELINE_STATUSES as VALID_BASELINE_STATUSES,
23
- DOC_STATUSES as VALID_DOC_STATUSES,
24
- COVERAGE_COLUMNS,
25
- DEFAULT_PATH_PATTERNS,
26
- DEFAULT_PREFIXES,
27
- MAIN_COLUMNS,
28
- REVIEW_STATUSES as VALID_REVIEW_STATUSES,
29
- SUMMARY_COLUMNS,
30
- normalize_completed_flag,
31
- normalize_doc_profile,
32
- normalize_status,
33
- normalize_text,
34
- )
35
-
36
-
37
- def normalize_path_segment(value):
38
- value = normalize_text(value)
39
- if not value:
40
- return "Unclassified"
41
- value = re.sub(r'[<>:"/\\|?*]', " ", value)
42
- value = re.sub(r"\s+", " ", value).strip()
43
- return value or "Unclassified"
44
-
45
-
46
- def markdown_escape(value):
47
- return normalize_text(value).replace("|", "\\|")
48
-
49
-
50
- def detect_delimiter(path):
51
- suffix = path.suffix.lower()
52
- if suffix == ".tsv":
53
- return "\t"
54
- return ","
55
-
56
-
57
- def read_rows(path):
58
- if path is None:
59
- return []
60
- path = Path(path)
61
- if not path.exists():
62
- raise SystemExit(f"Input file not found: {path}")
63
- suffix = path.suffix.lower()
64
- if suffix == ".jsonl":
65
- rows = []
66
- with path.open("r", encoding="utf-8-sig", newline="") as handle:
67
- for line_no, line in enumerate(handle, 1):
68
- line = line.strip()
69
- if not line:
70
- continue
71
- try:
72
- value = json.loads(line)
73
- except json.JSONDecodeError as exc:
74
- raise SystemExit(f"{path}:{line_no}: invalid JSONL: {exc}") from exc
75
- if not isinstance(value, dict):
76
- raise SystemExit(f"{path}:{line_no}: JSONL row must be an object")
77
- rows.append(value)
78
- return rows
79
- if suffix == ".json":
80
- with path.open("r", encoding="utf-8-sig") as handle:
81
- value = json.load(handle)
82
- if isinstance(value, dict):
83
- for key in ("rows", "targets", "items"):
84
- if key in value:
85
- value = value[key]
86
- break
87
- if not isinstance(value, list) or any(not isinstance(row, dict) for row in value):
88
- raise SystemExit(f"{path}: JSON input must be an array of objects or an object with rows")
89
- return value
90
- delimiter = detect_delimiter(path)
91
- with path.open("r", encoding="utf-8-sig", newline="") as handle:
92
- return list(csv.DictReader(handle, delimiter=delimiter))
93
-
94
-
95
- def write_csv(path, rows, columns):
96
- path = Path(path)
97
- path.parent.mkdir(parents=True, exist_ok=True)
98
- with path.open("w", encoding="utf-8", newline="") as handle:
99
- writer = csv.DictWriter(handle, fieldnames=columns)
100
- writer.writeheader()
101
- for row in rows:
102
- writer.writerow({column: row.get(column, "") for column in columns})
103
-
104
-
105
- def parse_prefix_args(values):
106
- prefixes = dict(DEFAULT_PREFIXES)
107
- for value in values or []:
108
- if "=" not in value:
109
- raise SystemExit(f"Invalid --prefix value: {value}. Expected source_type=PREFIX.")
110
- source_type, prefix = value.split("=", 1)
111
- source_type = normalize_text(source_type)
112
- prefix = normalize_text(prefix).upper()
113
- if not source_type or not re.fullmatch(r"[A-Z][A-Z0-9]*", prefix):
114
- raise SystemExit(f"Invalid --prefix value: {value}.")
115
- prefixes[source_type] = prefix
116
- return prefixes
117
-
118
-
119
- def row_get(row, *names):
120
- lowered = {str(key).lower(): key for key in row.keys()}
121
- for name in names:
122
- key = lowered.get(name.lower())
123
- if key is not None:
124
- return row.get(key)
125
- return ""
126
-
127
-
128
- def normalize_targets(rows, prefixes):
129
- normalized = []
130
- for index, row in enumerate(rows, 1):
131
- source_type = normalize_text(row_get(row, "source_type", "type"))
132
- group = normalize_text(row_get(row, "group", "group_name", "module", "category")) or "Unclassified"
133
- name = normalize_text(row_get(row, "name", "display_name", "label", "title"))
134
- entrypoint = normalize_text(row_get(row, "entrypoint", "activation_target", "route", "path", "command"))
135
- keyword = normalize_text(row_get(row, "keyword", "stable_key", "key", "code", "id"))
136
- document_path = normalize_text(row_get(row, "document_path", "doc_path"))
137
- baseline_status = normalize_status(
138
- row_get(row, "baseline_status"),
139
- VALID_BASELINE_STATUSES,
140
- "pending",
141
- )
142
- foundation_doc_status = normalize_status(
143
- row_get(row, "foundation_doc_status"),
144
- VALID_DOC_STATUSES,
145
- "pending",
146
- )
147
- architecture_doc_status = normalize_status(
148
- row_get(row, "architecture_doc_status"),
149
- VALID_DOC_STATUSES,
150
- "pending",
151
- )
152
- ops_doc_status = normalize_status(
153
- row_get(row, "ops_doc_status"),
154
- VALID_DOC_STATUSES,
155
- "pending",
156
- )
157
- review_status = normalize_status(
158
- row_get(row, "review_status"), VALID_REVIEW_STATUSES, "not_started"
159
- )
160
- completed_flag = normalize_completed_flag(
161
- row_get(row, "document_completed_flag(Y/N)")
162
- )
163
- if not source_type:
164
- raise SystemExit(f"Target row {index}: missing source_type")
165
- if source_type not in prefixes:
166
- raise SystemExit(
167
- f"Target row {index}: source_type '{source_type}' has no prefix. "
168
- "Register the activation surface in Source Type Registry or pass "
169
- "--prefix source_type=PREFIX for this converter run."
170
- )
171
- if not name:
172
- raise SystemExit(f"Target row {index}: missing name")
173
- if not keyword:
174
- keyword = entrypoint or name
175
- if not entrypoint:
176
- entrypoint = keyword
177
- if not document_path:
178
- pattern = DEFAULT_PATH_PATTERNS.get(source_type, "docs/{source_type}/{name}")
179
- document_path = pattern.format(
180
- source_type=normalize_path_segment(source_type),
181
- group=normalize_path_segment(group),
182
- name=normalize_path_segment(name),
183
- )
184
- doc_profile = normalize_doc_profile(row_get(row, "doc_profile"), source_type)
185
- normalized.append(
186
- {
187
- "id": normalize_text(row_get(row, "id")),
188
- "source_type": source_type,
189
- "original_id": normalize_text(row_get(row, "original_id")),
190
- "group": group,
191
- "name": name,
192
- "entrypoint": entrypoint,
193
- "document_path": document_path,
194
- "keyword": keyword,
195
- "doc_profile": doc_profile,
196
- "baseline_status": baseline_status,
197
- "foundation_doc_status": foundation_doc_status,
198
- "architecture_doc_status": architecture_doc_status,
199
- "ops_doc_status": ops_doc_status,
200
- "review_status": review_status,
201
- "document_completed_flag(Y/N)": completed_flag,
202
- "last_handoff": normalize_text(row_get(row, "last_handoff")),
203
- "notes": normalize_text(row_get(row, "notes")),
204
- }
205
- )
206
- return dedupe_targets(normalized)
207
-
208
-
209
- def dedupe_targets(rows):
210
- deduped = OrderedDict()
211
- for row in rows:
212
- key = (
213
- row["source_type"].casefold(),
214
- row["keyword"].casefold(),
215
- row["entrypoint"].casefold(),
216
- )
217
- if key not in deduped:
218
- deduped[key] = row
219
- continue
220
- current = deduped[key]
221
- for column in MAIN_COLUMNS:
222
- if not current.get(column) and row.get(column):
223
- current[column] = row[column]
224
- return list(deduped.values())
225
-
226
-
227
- def target_sort_key(row):
228
- return (
229
- normalize_text(row.get("source_type", "")).casefold(),
230
- normalize_text(row.get("group", "")).casefold(),
231
- normalize_text(row.get("name", "")).casefold(),
232
- normalize_text(row.get("entrypoint", "")).casefold(),
233
- normalize_text(row.get("keyword", "")).casefold(),
234
- )
235
-
236
-
237
- def sort_targets_for_id_assignment(rows):
238
- return sorted(rows, key=target_sort_key)
239
-
240
-
241
- def parse_existing_ids(path):
242
- if path is None or not Path(path).exists():
243
- return {}, defaultdict(set)
244
- rows = parse_markdown_main_table(Path(path))
245
- mapping = {}
246
- used = defaultdict(set)
247
- for row in rows:
248
- source_type = row.get("source_type", "")
249
- original_id = row.get("original_id", "")
250
- if source_type and original_id.isdigit():
251
- used[source_type].add(int(original_id))
252
- for key in target_keys(row):
253
- mapping[key] = {
254
- "id": row.get("id", ""),
255
- "original_id": row.get("original_id", ""),
256
- }
257
- return mapping, used
258
-
259
-
260
- def parse_markdown_main_table(path):
261
- rows = []
262
- lines = path.read_text(encoding="utf-8-sig").splitlines()
263
- for index, line in enumerate(lines):
264
- cells = split_markdown_row(line)
265
- if cells == MAIN_COLUMNS:
266
- columns = cells
267
- if index + 1 >= len(lines):
268
- return rows
269
- for data_line in lines[index + 2 :]:
270
- if not data_line.strip().startswith("|"):
271
- break
272
- values = split_markdown_row(data_line)
273
- if len(values) != len(columns):
274
- break
275
- row = dict(zip(columns, values))
276
- rows.append(normalize_existing_row(row))
277
- break
278
- return rows
279
-
280
-
281
- def normalize_existing_row(row):
282
- normalized = {column: row.get(column, "") for column in MAIN_COLUMNS}
283
- normalized["doc_profile"] = normalize_doc_profile(
284
- normalized.get("doc_profile", ""), normalized.get("source_type", "")
285
- )
286
- return normalized
287
-
288
-
289
- def split_markdown_row(line):
290
- line = line.strip()
291
- if not line.startswith("|") or not line.endswith("|"):
292
- return []
293
- cells = []
294
- current = []
295
- escaped = False
296
- for char in line[1:-1]:
297
- if escaped:
298
- current.append(char)
299
- escaped = False
300
- elif char == "\\":
301
- escaped = True
302
- elif char == "|":
303
- cells.append("".join(current).strip())
304
- current = []
305
- else:
306
- current.append(char)
307
- cells.append("".join(current).strip())
308
- return cells
309
-
310
-
311
- def target_keys(row):
312
- source_type = normalize_text(row.get("source_type", "")).casefold()
313
- keyword = normalize_text(row.get("keyword", "")).casefold()
314
- entrypoint = normalize_text(row.get("entrypoint", "")).casefold()
315
- name = normalize_text(row.get("name", "")).casefold()
316
- keys = []
317
- if source_type and keyword:
318
- keys.append((source_type, "keyword", keyword))
319
- if source_type and entrypoint and name:
320
- keys.append((source_type, "entrypoint-name", entrypoint, name))
321
- return keys
322
-
323
-
324
- def assign_ids(rows, prefixes, existing_mapping, used_original_ids):
325
- used_ids = set()
326
- next_ids = {}
327
- for source_type, values in used_original_ids.items():
328
- next_ids[source_type] = max(values) + 1 if values else 1
329
-
330
- for row in rows:
331
- preserved = None
332
- for key in target_keys(row):
333
- preserved = existing_mapping.get(key)
334
- if preserved:
335
- break
336
- source_type = row["source_type"]
337
- prefix = prefixes[source_type]
338
- if preserved and preserved.get("id") and preserved["id"] not in used_ids:
339
- row["id"] = preserved["id"]
340
- row["original_id"] = preserved.get("original_id", row.get("original_id", ""))
341
- elif row.get("id") and row["id"] not in used_ids:
342
- row["original_id"] = row.get("original_id") or strip_prefix(row["id"], prefix)
343
- else:
344
- next_value = next_ids.get(source_type, 1)
345
- while next_value in used_original_ids[source_type]:
346
- next_value += 1
347
- row["original_id"] = str(next_value)
348
- row["id"] = f"{prefix}{next_value}"
349
- used_original_ids[source_type].add(next_value)
350
- next_ids[source_type] = next_value + 1
351
- if not row["original_id"].isdigit():
352
- raise SystemExit(f"Row '{row['name']}' has non-numeric original_id: {row['original_id']}")
353
- expected_prefix = prefixes[source_type]
354
- expected_id = f"{expected_prefix}{row['original_id']}"
355
- if row["id"] != expected_id:
356
- raise SystemExit(
357
- f"Row '{row['name']}' id does not match source_type prefix and original_id: "
358
- f"{row['id']} != {expected_id}"
359
- )
360
- if row["id"] in used_ids:
361
- raise SystemExit(f"Duplicate id after assignment: {row['id']}")
362
- used_ids.add(row["id"])
363
- return rows
364
-
365
-
366
- def strip_prefix(value, prefix):
367
- value = normalize_text(value)
368
- if value.startswith(prefix):
369
- return value[len(prefix) :]
370
- return ""
371
-
372
-
373
- def normalize_summary(rows):
374
- normalized = []
375
- for index, row in enumerate(rows, 1):
376
- item = {column: normalize_text(row_get(row, column)) for column in SUMMARY_COLUMNS}
377
- for column in ("raw_count", "eligible_count", "excluded_count", "gap_count"):
378
- if item[column] == "":
379
- raise SystemExit(f"Summary row {index}: missing {column}")
380
- if not item[column].isdigit():
381
- raise SystemExit(f"Summary row {index}: {column} must be a non-negative integer")
382
- raw = int(item["raw_count"])
383
- eligible = int(item["eligible_count"])
384
- excluded = int(item["excluded_count"])
385
- gap = int(item["gap_count"])
386
- if raw != eligible + excluded + gap:
387
- raise SystemExit(
388
- f"Summary row {index}: raw_count must equal eligible_count + excluded_count + gap_count"
389
- )
390
- normalized.append(item)
391
- return normalized
392
-
393
-
394
- def normalize_coverage(rows):
395
- normalized = []
396
- for row in rows:
397
- item = {column: normalize_text(row_get(row, column)) for column in COVERAGE_COLUMNS}
398
- if any(item.values()):
399
- normalized.append(item)
400
- return normalized
401
-
402
-
403
- def validate_main_counts(rows, summary_rows):
404
- main_count = len(rows)
405
- eligible_count = sum(int(row["eligible_count"]) for row in summary_rows)
406
- if eligible_count != main_count:
407
- raise SystemExit(
408
- f"Eligible count mismatch: summary eligible_count total is {eligible_count}, "
409
- f"but main target rows are {main_count}."
410
- )
411
-
412
-
413
- def is_canonical_queue_output(output_path):
414
- return (
415
- output_path.name == "docs-target-queue.md"
416
- and output_path.parent.name.casefold() == "docs"
417
- )
418
-
419
-
420
- def is_target_staging_output(output_path):
421
- return (
422
- output_path.suffix.lower() == ".md"
423
- and "target" in {part.casefold() for part in output_path.parts}
424
- )
425
-
426
-
427
- def validate_output_path(output_path, partial_output):
428
- if is_canonical_queue_output(output_path):
429
- return
430
- if partial_output and is_target_staging_output(output_path):
431
- return
432
- if partial_output:
433
- raise SystemExit(
434
- "--partial-output only permits staging Markdown output under target/**. "
435
- "Final queue output must be docs/docs-target-queue.md."
436
- )
437
- raise SystemExit(
438
- "Final queue output must be docs/docs-target-queue.md. "
439
- "For partial candidates, pass --partial-output and write under "
440
- "target/docs-target-queue-from-catalog/."
441
- )
442
-
443
-
444
- def build_obsidian_links(output_path):
445
- links = []
446
- if output_path.name == "docs-target-queue.md":
447
- catalog_path = output_path.with_name("docs-target-catalog.md")
448
- if catalog_path.exists():
449
- links.append("- [[docs-target-catalog]]")
450
- return links
451
-
452
-
453
- def build_markdown(rows, summary_rows, coverage_rows, source_note, output_path):
454
- lines = []
455
- lines.extend(["# Documentation Target Queue", ""])
456
- lines.append(f"Source note: {source_note}")
457
- obsidian_links = build_obsidian_links(output_path)
458
- if obsidian_links:
459
- lines.extend(["", "## Obsidian Links", ""])
460
- lines.extend(obsidian_links)
461
- lines.extend(["", "## Source Acquisition Summary", ""])
462
- append_table(lines, SUMMARY_COLUMNS, summary_rows)
463
- lines.extend(["", "## Main Target Table", ""])
464
- append_table(lines, MAIN_COLUMNS, rows)
465
- lines.extend(["", "## Source-Type Counts", ""])
466
- count_rows = []
467
- counts = defaultdict(int)
468
- for row in rows:
469
- counts[row["source_type"]] += 1
470
- for source_type in sorted(counts):
471
- count_rows.append({"source_type": source_type, "count": str(counts[source_type])})
472
- count_rows.append({"source_type": "Total", "count": str(len(rows))})
473
- append_table(lines, ["source_type", "count"], count_rows)
474
- lines.extend(["", "## Coverage Review", ""])
475
- append_table(lines, COVERAGE_COLUMNS, coverage_rows)
476
- lines.append("")
477
- return "\n".join(lines)
478
-
479
-
480
- def append_table(lines, columns, rows):
481
- lines.append("| " + " | ".join(columns) + " |")
482
- lines.append("| " + " | ".join("---" for _ in columns) + " |")
483
- for row in rows:
484
- values = [markdown_escape(row.get(column, "")) for column in columns]
485
- lines.append("| " + " | ".join(values) + " |")
486
-
487
-
488
- def main():
489
- parser = argparse.ArgumentParser(
490
- description="Generate docs/docs-target-queue.md from normalized target, summary, and coverage inputs."
491
- )
492
- parser.add_argument("--targets", required=True, help="CSV/TSV/JSON/JSONL normalized target rows.")
493
- parser.add_argument("--summary", required=True, help="CSV/TSV/JSON/JSONL source acquisition summary rows.")
494
- parser.add_argument("--coverage", help="CSV/TSV/JSON/JSONL coverage review rows.")
495
- parser.add_argument("--existing", help="Existing docs/docs-target-queue.md used to preserve IDs.")
496
- parser.add_argument("--output", required=True, help="Output docs/docs-target-queue.md path.")
497
- parser.add_argument("--source-note", required=True, help="Short source note for the generated Markdown.")
498
- parser.add_argument(
499
- "--partial-output",
500
- action="store_true",
501
- help=(
502
- "Allow writing a partial/staging Markdown output under target/**. "
503
- "Final output remains docs/docs-target-queue.md."
504
- ),
505
- )
506
- parser.add_argument(
507
- "--prefix",
508
- action="append",
509
- default=[],
510
- help=(
511
- "Registration extension for a source_type prefix in source_type=PREFIX form. "
512
- "Use for activation surfaces not yet in the shared registry. May be repeated."
513
- ),
514
- )
515
- parser.add_argument(
516
- "--write-normalized-targets",
517
- help="Optional CSV path for the post-validation target rows with assigned IDs.",
518
- )
519
- args = parser.parse_args()
520
-
521
- output = Path(args.output)
522
- validate_output_path(output, args.partial_output)
523
-
524
- prefixes = parse_prefix_args(args.prefix)
525
- targets = normalize_targets(read_rows(args.targets), prefixes)
526
- summary = normalize_summary(read_rows(args.summary))
527
- coverage = normalize_coverage(read_rows(args.coverage) if args.coverage else [])
528
- existing_mapping, used_original_ids = parse_existing_ids(args.existing)
529
- targets = sort_targets_for_id_assignment(targets)
530
- targets = assign_ids(targets, prefixes, existing_mapping, used_original_ids)
531
- validate_main_counts(targets, summary)
532
-
533
- output.parent.mkdir(parents=True, exist_ok=True)
534
- output.write_text(build_markdown(targets, summary, coverage, args.source_note, output), encoding="utf-8", newline="\n")
535
- if args.write_normalized_targets:
536
- write_csv(args.write_normalized_targets, targets, MAIN_COLUMNS)
537
- print(f"Wrote {output} with {len(targets)} target rows.")
538
-
539
-
540
- if __name__ == "__main__":
541
- try:
542
- main()
543
- except BrokenPipeError:
544
- sys.exit(1)