@jenga-ai/agent 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +10 -7
  2. package/agents/developer.md +82 -2
  3. package/agents/scrum-master.md +215 -21
  4. package/agents/tester.md +90 -8
  5. package/hooks/on_session_end.sh +171 -20
  6. package/mcp/router/embedder.js +1 -1
  7. package/mcp/training_runner/index.js +239 -0
  8. package/mcp/training_runner/package-lock.json +1065 -0
  9. package/mcp/training_runner/package.json +15 -0
  10. package/package.json +14 -16
  11. package/scripts/check-permission-level.sh +107 -0
  12. package/scripts/check-publicignore-match.sh +122 -0
  13. package/scripts/check-worktree-liveness.sh +193 -0
  14. package/scripts/generate-rapport-manifest.sh +43 -0
  15. package/scripts/idea_manager.sh +47 -0
  16. package/scripts/install-worktree-commit-guard.sh +134 -0
  17. package/scripts/jenga-permission-level-switch.sh +109 -0
  18. package/scripts/smoke-harness.sh +139 -0
  19. package/scripts/validate-board.sh +62 -0
  20. package/scripts/with-lock.sh +158 -0
  21. package/scripts/worktree-remove-guard.sh +204 -0
  22. package/skills/clearify/SKILL.md +52 -0
  23. package/skills/close-story/SKILL.md +203 -0
  24. package/skills/close-story/scripts/check-story-closeable.sh +195 -0
  25. package/skills/close-story/scripts/compute-scope-divergence.sh +128 -0
  26. package/skills/close-story/scripts/extract-diff-stats.sh +48 -0
  27. package/skills/close-story/scripts/extract-task-diff-stats.sh +97 -0
  28. package/skills/close-story/scripts/update-task-frontmatter.sh +103 -0
  29. package/skills/commit/SKILL.md +30 -3
  30. package/skills/distribute/CONFIG_SCHEMA.md +148 -0
  31. package/skills/distribute/SKILL.md +173 -0
  32. package/skills/distribute/scripts/check-version.sh +74 -0
  33. package/skills/distribute/scripts/commit-version-bump.sh +108 -0
  34. package/skills/distribute/scripts/distribute-changes.sh +381 -0
  35. package/skills/do/SKILL.md +352 -1
  36. package/skills/do/assets/intent-vs-diff-prompt.md +69 -0
  37. package/skills/doc/assets/path-objectives.yaml +13 -0
  38. package/skills/doc-sync/SKILL.md +16 -0
  39. package/skills/doc-sync/assets/doc_targets.md +11 -0
  40. package/skills/idea/SKILL.md +56 -0
  41. package/skills/idea/assets/idea_handoff_template.md +26 -0
  42. package/skills/idea/assets/idea_template.md +3 -0
  43. package/skills/init/SKILL.md +101 -7
  44. package/skills/init/assets/directory_structure.txt +1 -0
  45. package/skills/init/assets/strategy_stub_template.md +38 -0
  46. package/skills/init/assets/workflow_template.json +1 -1
  47. package/skills/init/scripts/apply-project-visibility.sh +176 -0
  48. package/skills/init/scripts/detect-existing-codebase.sh +166 -0
  49. package/skills/init/scripts/init.sh +35 -1
  50. package/skills/jenga/SKILL.md +206 -14
  51. package/skills/jenga/scripts/board-scan.sh +238 -0
  52. package/skills/jenga/scripts/cascade-resolve.sh +297 -0
  53. package/skills/jenga/scripts/render-confirmation.sh +679 -0
  54. package/skills/jenga/scripts/render-picker.sh +439 -0
  55. package/skills/jenga/scripts/resolve-id.sh +367 -0
  56. package/skills/jenga-permission-level/SKILL.md +81 -0
  57. package/skills/proceed/SKILL.md +1 -1
  58. package/skills/publish/SKILL.md +8 -5
  59. package/skills/publish/assets/ci-contract.md +2 -2
  60. package/skills/publish/assets/ownership-matrix.md +1 -1
  61. package/skills/publish/scripts/finalize_changelog.sh +115 -0
  62. package/skills/publish/scripts/generate_release_notes.sh +475 -28
  63. package/skills/publish/scripts/npm_ci_pipeline.sh +44 -6
  64. package/skills/publish/scripts/publish_deploy.sh +38 -8
  65. package/skills/publish/scripts/run_gates.sh +2 -2
  66. package/skills/reconcile/SKILL.md +117 -5
  67. package/skills/reconcile/scripts/detect-unlinked-code.sh +741 -0
  68. package/skills/skillify/assets/init-new/assets/directory_structure.txt +5 -1
  69. package/skills/spinoff/SKILL.md +12 -7
  70. package/skills/todo/SKILL.md +2 -0
  71. package/skills/uncharted/SKILL.md +711 -0
  72. package/skills/uncharted/assets/SEGMENT_PROPOSAL_TEMPLATE.md +129 -0
  73. package/skills/uncharted/assets/UNDERSTANDING_DOC_TEMPLATE.md +160 -0
  74. package/skills/uncharted/scripts/apply-subsystem-cap.sh +573 -0
  75. package/skills/uncharted/scripts/detect-dependencies.sh +732 -0
  76. package/skills/uncharted/scripts/detect-tests.sh +553 -0
  77. package/skills/uncharted/scripts/discover-subsystems.sh +1029 -0
  78. package/skills/uncharted/scripts/enumerate-target.sh +470 -0
  79. package/skills/uncharted/scripts/import-source.sh +517 -0
  80. package/skills/uncharted/scripts/inspect-provenance.sh +573 -0
  81. package/skills/uncharted/scripts/resolve-segment-target.sh +640 -0
  82. package/skills/uncharted/scripts/run-engine.sh +655 -0
  83. package/skills/uncharted/scripts/validate-proposed-items.sh +125 -0
  84. package/skills/uncharted/scripts/write-backfilled-epics.sh +498 -0
  85. package/skills/wtf/SKILL.md +20 -0
  86. package/templates/CHANGELOG_TEMPLATE.md +13 -0
  87. package/templates/PROBLEM_RAPPORT_TEMPLATE.md +4 -1
  88. package/templates/SCRUM_BOARD_SCHEMA.md +206 -10
  89. package/templates/permission-levels/README.md +73 -0
  90. package/templates/permission-levels/level-1-locked.json +71 -0
  91. package/templates/permission-levels/level-2-guarded.json +64 -0
  92. package/templates/permission-levels/level-3-standard.json +62 -0
  93. package/templates/permission-levels/level-4-elevated.json +60 -0
  94. package/templates/permission-levels/level-5-unrestricted.json +58 -0
  95. package/skills/convert/SKILL.md +0 -124
  96. package/skills/convert/convert_cli.py +0 -235
  97. package/skills/convert/tests/sample.csv +0 -4
  98. package/skills/convert/tests/sample.json +0 -5
  99. package/skills/convert/tests/sample.jsonl +0 -3
  100. package/skills/convert/tests/sample.yaml +0 -18
  101. package/skills/convert/tests/sample_obj.csv +0 -2
  102. package/skills/convert/tests/sample_obj.json +0 -9
  103. package/skills/mirror-public/SKILL.md +0 -237
  104. package/skills/mirror-public/assets/config.json +0 -5
  105. package/skills/mirror-public/scripts/mirror.sh +0 -374
  106. package/skills/self-sync/SKILL.md +0 -73
  107. package/skills/self-sync/scripts/run.js +0 -136
  108. package/skills/train/SKILL.md +0 -116
  109. package/skills/train/assets/dashboard-templates/classifiers.html +0 -106
  110. package/skills/train/assets/dashboard-templates/nlp.html +0 -102
  111. package/skills/train/assets/dashboard-templates/transformers.html +0 -98
  112. package/skills/train/assets/results-parsers/__init__.py +0 -9
  113. package/skills/train/assets/results-parsers/classifiers.py +0 -84
  114. package/skills/train/assets/results-parsers/nlp.py +0 -88
  115. package/skills/train/assets/results-parsers/reporter.py +0 -154
  116. package/skills/train/assets/results-parsers/transformers.py +0 -120
  117. package/skills/train/train_cli.py +0 -786
@@ -1,84 +0,0 @@
1
- """
2
- classifiers.py — Results parser for sklearn-based classifier jobs.
3
-
4
- Extracts: accuracy, precision, recall, f1_score, model_type.
5
- Reads from results.json or scans training-results/ for JSON files.
6
- """
7
- import json
8
- from pathlib import Path
9
-
10
-
11
- def parse(job_dir: Path) -> dict:
12
- """
13
- Parse training results for a classifiers job.
14
-
15
- Returns a dict with keys:
16
- accuracy, precision, recall, f1_score, model_type
17
- Returns empty dict if no results can be found; never raises.
18
- """
19
- job_dir = Path(job_dir)
20
- metrics = {}
21
-
22
- # 1. Try results.json at root
23
- results_json = job_dir / "results.json"
24
- if results_json.exists():
25
- try:
26
- data = json.loads(results_json.read_text())
27
- metrics = _extract_from_dict(data)
28
- if metrics:
29
- return metrics
30
- except Exception:
31
- pass
32
-
33
- # 2. Scan training-results/ for any JSON files
34
- training_results_dir = job_dir / "training-results"
35
- if training_results_dir.exists():
36
- for json_file in sorted(training_results_dir.rglob("*.json")):
37
- try:
38
- data = json.loads(json_file.read_text())
39
- metrics = _extract_from_dict(data)
40
- if metrics:
41
- return metrics
42
- except Exception:
43
- continue
44
-
45
- return metrics
46
-
47
-
48
- def _extract_from_dict(data: dict) -> dict:
49
- """Extract classifier-relevant keys from a parsed dict."""
50
- metrics = {}
51
- if not isinstance(data, dict):
52
- return metrics
53
-
54
- # Common field names produced by sklearn classification_report / custom scripts
55
- field_map = {
56
- "accuracy": ["accuracy", "test_accuracy", "val_accuracy", "acc"],
57
- "precision": ["precision", "weighted avg.precision", "macro avg.precision"],
58
- "recall": ["recall", "weighted avg.recall", "macro avg.recall"],
59
- "f1_score": ["f1_score", "f1", "weighted avg.f1-score", "macro avg.f1-score"],
60
- "model_type": ["model_type", "model", "algorithm", "classifier"],
61
- }
62
-
63
- for target_key, candidates in field_map.items():
64
- for candidate in candidates:
65
- # Support dot-path lookup (e.g. "weighted avg.precision")
66
- value = _deep_get(data, candidate)
67
- if value is not None:
68
- metrics[target_key] = value
69
- break
70
-
71
- return metrics
72
-
73
-
74
- def _deep_get(data: dict, dotted_key: str):
75
- """Retrieve a value from a nested dict using a dot-separated key path."""
76
- keys = dotted_key.split(".")
77
- current = data
78
- for k in keys:
79
- if not isinstance(current, dict):
80
- return None
81
- current = current.get(k)
82
- if current is None:
83
- return None
84
- return current
@@ -1,88 +0,0 @@
1
- """
2
- nlp.py — Results parser for spaCy / NLP pipeline training jobs.
3
-
4
- Extracts: f1, precision, recall, model_name, task, iterations.
5
- Reads from results.json or scans training-results/ for JSON files.
6
- """
7
- import json
8
- from pathlib import Path
9
-
10
-
11
- def parse(job_dir: Path) -> dict:
12
- """
13
- Parse training results for an nlp job.
14
-
15
- Returns a dict with keys:
16
- f1, precision, recall, model_name, task, iterations
17
- Returns empty dict if no results can be found; never raises.
18
- """
19
- job_dir = Path(job_dir)
20
- metrics = {}
21
-
22
- # 1. Try results.json at root
23
- results_json = job_dir / "results.json"
24
- if results_json.exists():
25
- try:
26
- data = json.loads(results_json.read_text())
27
- metrics = _extract_from_dict(data)
28
- if metrics:
29
- return metrics
30
- except Exception:
31
- pass
32
-
33
- # 2. Scan training-results/ for JSON files
34
- training_results_dir = job_dir / "training-results"
35
- if training_results_dir.exists():
36
- for json_file in sorted(training_results_dir.rglob("*.json")):
37
- try:
38
- data = json.loads(json_file.read_text())
39
- metrics = _extract_from_dict(data)
40
- if metrics:
41
- return metrics
42
- except Exception:
43
- continue
44
-
45
- # 3. Try spaCy training output (scores.json or metrics.json)
46
- for scores_file in sorted(job_dir.rglob("scores.json")) + sorted(job_dir.rglob("metrics.json")):
47
- try:
48
- data = json.loads(scores_file.read_text())
49
- metrics = _extract_from_dict(data)
50
- if metrics:
51
- return metrics
52
- except Exception:
53
- continue
54
-
55
- return metrics
56
-
57
-
58
- def _extract_from_dict(data: dict) -> dict:
59
- """Extract NLP-relevant keys from a parsed dict."""
60
- metrics = {}
61
- if not isinstance(data, dict):
62
- return metrics
63
-
64
- field_map = {
65
- "f1": ["f1", "f1_score", "ents_f", "token_f", "tag_f", "sents_f", "score"],
66
- "precision": ["precision", "ents_p", "token_p", "tag_p"],
67
- "recall": ["recall", "ents_r", "token_r", "tag_r"],
68
- "model_name": ["model_name", "model", "base_model"],
69
- "task": ["task", "pipeline_component", "component"],
70
- "iterations": ["iterations", "n_iter", "steps", "batches_trained"],
71
- }
72
-
73
- for target_key, candidates in field_map.items():
74
- for candidate in candidates:
75
- value = data.get(candidate)
76
- if value is None:
77
- # Try nested under "scores" or "results" key
78
- for wrapper in ("scores", "results", "metrics"):
79
- nested = data.get(wrapper, {})
80
- if isinstance(nested, dict):
81
- value = nested.get(candidate)
82
- if value is not None:
83
- break
84
- if value is not None:
85
- metrics[target_key] = value
86
- break
87
-
88
- return metrics
@@ -1,154 +0,0 @@
1
- """
2
- reporter.py — Surface training results to terminal, summary.md, and results.json.
3
-
4
- Usage:
5
- from skills.train.assets.results_parsers.reporter import surface_results
6
- surface_results(job_dir, job_type)
7
- """
8
- import importlib
9
- import json
10
- import sys
11
- from datetime import datetime, timezone
12
- from pathlib import Path
13
-
14
- _PARSER_PKG = Path(__file__).resolve().parent
15
- sys.path.insert(0, str(_PARSER_PKG.parent.parent.parent)) # ensure skills/ is on path
16
-
17
-
18
- def _load_parser(job_type: str):
19
- """Dynamically import the type-specific parser module."""
20
- try:
21
- spec_path = _PARSER_PKG / f"{job_type}.py"
22
- import importlib.util
23
- spec = importlib.util.spec_from_file_location(job_type, spec_path)
24
- mod = importlib.util.module_from_spec(spec)
25
- spec.loader.exec_module(mod)
26
- return mod
27
- except Exception as e:
28
- return None
29
-
30
-
31
- def _format_value(v) -> str:
32
- if isinstance(v, float):
33
- return f"{v:.4f}"
34
- return str(v)
35
-
36
-
37
- def _print_terminal_summary(job_dir: Path, job_type: str, metrics: dict):
38
- """Print a box-formatted summary to stdout."""
39
- job_name = job_dir.name
40
- width = 54
41
- border = "─" * width
42
- print(f"\n┌{border}┐")
43
- print(f"│ 📊 Training Summary — {job_name:<{width - 25}}│")
44
- print(f"│ Type: {job_type:<{width - 9}}│")
45
- print(f"├{border}┤")
46
- if metrics:
47
- for k, v in metrics.items():
48
- label = k.replace("_", " ").title()
49
- value = _format_value(v)
50
- line = f" {label}: {value}"
51
- print(f"│{line:<{width + 1}}│")
52
- else:
53
- print(f"│ (no metrics found — check training-results/ or results.json) │")
54
- print(f"└{border}┘\n")
55
-
56
-
57
- def _write_summary_md(job_dir: Path, job_type: str, metrics: dict):
58
- """Write a Markdown summary file to the job directory."""
59
- job_name = job_dir.name
60
- timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
61
- lines = [
62
- f"# Training Summary — {job_name}",
63
- f"",
64
- f"**Type:** {job_type} ",
65
- f"**Generated:** {timestamp} ",
66
- f"",
67
- f"## Metrics",
68
- f"",
69
- f"| Metric | Value |",
70
- f"|--------|-------|",
71
- ]
72
- if metrics:
73
- for k, v in metrics.items():
74
- label = k.replace("_", " ").title()
75
- lines.append(f"| {label} | {_format_value(v)} |")
76
- else:
77
- lines.append("| — | No metrics found |")
78
-
79
- summary_path = job_dir / "summary.md"
80
- summary_path.write_text("\n".join(lines) + "\n")
81
- return summary_path
82
-
83
-
84
- def _write_results_json(job_dir: Path, job_type: str, metrics: dict):
85
- """Write or update results.json in the job directory."""
86
- results_path = job_dir / "results.json"
87
-
88
- # Merge with existing results.json if present
89
- existing = {}
90
- if results_path.exists():
91
- try:
92
- existing = json.loads(results_path.read_text())
93
- except Exception:
94
- pass
95
-
96
- existing.update({
97
- "job_name": job_dir.name,
98
- "job_type": job_type,
99
- "generated_at": datetime.now(timezone.utc).isoformat(),
100
- "metrics": metrics,
101
- })
102
-
103
- results_path.write_text(json.dumps(existing, indent=2))
104
- return results_path
105
-
106
-
107
- def surface_results(job_dir, job_type: str):
108
- """
109
- Parse training results for the given job and surface them:
110
- 1. Print terminal summary
111
- 2. Write summary.md to job directory
112
- 3. Write/update results.json in job directory
113
-
114
- Args:
115
- job_dir: Path (or str) to the job directory
116
- job_type: One of 'classifiers', 'transformers', 'nlp'
117
- """
118
- job_dir = Path(job_dir)
119
-
120
- parser = _load_parser(job_type)
121
- if parser is None:
122
- print(f"⚠️ No parser found for type '{job_type}' — skipping result surfacing.")
123
- return {}
124
-
125
- try:
126
- metrics = parser.parse(job_dir) or {}
127
- except Exception as e:
128
- print(f"⚠️ Parser error for '{job_type}': {e}")
129
- metrics = {}
130
-
131
- _print_terminal_summary(job_dir, job_type, metrics)
132
-
133
- try:
134
- summary_path = _write_summary_md(job_dir, job_type, metrics)
135
- print(f"📄 summary.md written to {summary_path}")
136
- except Exception as e:
137
- print(f"⚠️ Could not write summary.md: {e}")
138
-
139
- try:
140
- results_path = _write_results_json(job_dir, job_type, metrics)
141
- print(f"📄 results.json written to {results_path}")
142
- except Exception as e:
143
- print(f"⚠️ Could not write results.json: {e}")
144
-
145
- return metrics
146
-
147
-
148
- if __name__ == "__main__":
149
- import argparse
150
- p = argparse.ArgumentParser(description="Surface training results for a job directory.")
151
- p.add_argument("job_dir", help="Path to the job directory")
152
- p.add_argument("job_type", choices=["classifiers", "transformers", "nlp"])
153
- ns = p.parse_args()
154
- surface_results(ns.job_dir, ns.job_type)
@@ -1,120 +0,0 @@
1
- """
2
- transformers.py — Results parser for HuggingFace Transformers fine-tuning jobs.
3
-
4
- Extracts: perplexity, eval_loss, train_loss, epochs, model_name.
5
- Reads from results.json or scans training-results/ and trainer_state.json.
6
- """
7
- import json
8
- import math
9
- from pathlib import Path
10
-
11
-
12
- def parse(job_dir: Path) -> dict:
13
- """
14
- Parse training results for a transformers job.
15
-
16
- Returns a dict with keys:
17
- perplexity, eval_loss, train_loss, epochs, model_name
18
- Returns empty dict if no results can be found; never raises.
19
- """
20
- job_dir = Path(job_dir)
21
- metrics = {}
22
-
23
- # 1. Try results.json at root
24
- results_json = job_dir / "results.json"
25
- if results_json.exists():
26
- try:
27
- data = json.loads(results_json.read_text())
28
- metrics = _extract_from_dict(data)
29
- if metrics:
30
- return metrics
31
- except Exception:
32
- pass
33
-
34
- # 2. Try trainer_state.json (HuggingFace Trainer output)
35
- for trainer_state in sorted(job_dir.rglob("trainer_state.json")):
36
- try:
37
- data = json.loads(trainer_state.read_text())
38
- metrics = _extract_from_trainer_state(data)
39
- if metrics:
40
- return metrics
41
- except Exception:
42
- continue
43
-
44
- # 3. Scan training-results/ for JSON files
45
- training_results_dir = job_dir / "training-results"
46
- if training_results_dir.exists():
47
- for json_file in sorted(training_results_dir.rglob("*.json")):
48
- try:
49
- data = json.loads(json_file.read_text())
50
- metrics = _extract_from_dict(data)
51
- if metrics:
52
- return metrics
53
- except Exception:
54
- continue
55
-
56
- return metrics
57
-
58
-
59
- def _extract_from_dict(data: dict) -> dict:
60
- """Extract transformer-relevant keys from a parsed dict."""
61
- metrics = {}
62
- if not isinstance(data, dict):
63
- return metrics
64
-
65
- field_map = {
66
- "eval_loss": ["eval_loss", "validation_loss", "val_loss"],
67
- "train_loss": ["train_loss", "training_loss", "loss"],
68
- "epochs": ["epochs", "num_train_epochs", "epoch"],
69
- "model_name": ["model_name", "model", "base_model", "pretrained_model_name_or_path"],
70
- "perplexity": ["perplexity", "eval_perplexity"],
71
- }
72
-
73
- for target_key, candidates in field_map.items():
74
- for candidate in candidates:
75
- value = data.get(candidate)
76
- if value is not None:
77
- metrics[target_key] = value
78
- break
79
-
80
- # Derive perplexity from eval_loss if not already present
81
- if "perplexity" not in metrics and "eval_loss" in metrics:
82
- try:
83
- metrics["perplexity"] = round(math.exp(float(metrics["eval_loss"])), 4)
84
- except (ValueError, OverflowError):
85
- pass
86
-
87
- return metrics
88
-
89
-
90
- def _extract_from_trainer_state(data: dict) -> dict:
91
- """Extract metrics from HuggingFace Trainer's trainer_state.json."""
92
- metrics = {}
93
- if not isinstance(data, dict):
94
- return metrics
95
-
96
- # Best metrics
97
- best_metric = data.get("best_metric")
98
- if best_metric is not None:
99
- metrics["eval_loss"] = best_metric
100
-
101
- # Epoch count
102
- epoch = data.get("epoch")
103
- if epoch is not None:
104
- metrics["epochs"] = epoch
105
-
106
- # Last eval from log history
107
- log_history = data.get("log_history", [])
108
- for entry in reversed(log_history):
109
- if "eval_loss" in entry:
110
- metrics["eval_loss"] = entry["eval_loss"]
111
- break
112
-
113
- # Derive perplexity
114
- if "perplexity" not in metrics and "eval_loss" in metrics:
115
- try:
116
- metrics["perplexity"] = round(math.exp(float(metrics["eval_loss"])), 4)
117
- except (ValueError, OverflowError):
118
- pass
119
-
120
- return metrics