@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.
- package/README.md +10 -7
- package/agents/developer.md +82 -2
- package/agents/scrum-master.md +215 -21
- package/agents/tester.md +90 -8
- package/hooks/on_session_end.sh +171 -20
- package/mcp/router/embedder.js +1 -1
- package/mcp/training_runner/index.js +239 -0
- package/mcp/training_runner/package-lock.json +1065 -0
- package/mcp/training_runner/package.json +15 -0
- package/package.json +14 -16
- package/scripts/check-permission-level.sh +107 -0
- package/scripts/check-publicignore-match.sh +122 -0
- package/scripts/check-worktree-liveness.sh +193 -0
- package/scripts/generate-rapport-manifest.sh +43 -0
- package/scripts/idea_manager.sh +47 -0
- package/scripts/install-worktree-commit-guard.sh +134 -0
- package/scripts/jenga-permission-level-switch.sh +109 -0
- package/scripts/smoke-harness.sh +139 -0
- package/scripts/validate-board.sh +62 -0
- package/scripts/with-lock.sh +158 -0
- package/scripts/worktree-remove-guard.sh +204 -0
- package/skills/clearify/SKILL.md +52 -0
- package/skills/close-story/SKILL.md +203 -0
- package/skills/close-story/scripts/check-story-closeable.sh +195 -0
- package/skills/close-story/scripts/compute-scope-divergence.sh +128 -0
- package/skills/close-story/scripts/extract-diff-stats.sh +48 -0
- package/skills/close-story/scripts/extract-task-diff-stats.sh +97 -0
- package/skills/close-story/scripts/update-task-frontmatter.sh +103 -0
- package/skills/commit/SKILL.md +30 -3
- package/skills/distribute/CONFIG_SCHEMA.md +148 -0
- package/skills/distribute/SKILL.md +173 -0
- package/skills/distribute/scripts/check-version.sh +74 -0
- package/skills/distribute/scripts/commit-version-bump.sh +108 -0
- package/skills/distribute/scripts/distribute-changes.sh +381 -0
- package/skills/do/SKILL.md +352 -1
- package/skills/do/assets/intent-vs-diff-prompt.md +69 -0
- package/skills/doc/assets/path-objectives.yaml +13 -0
- package/skills/doc-sync/SKILL.md +16 -0
- package/skills/doc-sync/assets/doc_targets.md +11 -0
- package/skills/idea/SKILL.md +56 -0
- package/skills/idea/assets/idea_handoff_template.md +26 -0
- package/skills/idea/assets/idea_template.md +3 -0
- package/skills/init/SKILL.md +101 -7
- package/skills/init/assets/directory_structure.txt +1 -0
- package/skills/init/assets/strategy_stub_template.md +38 -0
- package/skills/init/assets/workflow_template.json +1 -1
- package/skills/init/scripts/apply-project-visibility.sh +176 -0
- package/skills/init/scripts/detect-existing-codebase.sh +166 -0
- package/skills/init/scripts/init.sh +35 -1
- package/skills/jenga/SKILL.md +206 -14
- package/skills/jenga/scripts/board-scan.sh +238 -0
- package/skills/jenga/scripts/cascade-resolve.sh +297 -0
- package/skills/jenga/scripts/render-confirmation.sh +679 -0
- package/skills/jenga/scripts/render-picker.sh +439 -0
- package/skills/jenga/scripts/resolve-id.sh +367 -0
- package/skills/jenga-permission-level/SKILL.md +81 -0
- package/skills/proceed/SKILL.md +1 -1
- package/skills/publish/SKILL.md +8 -5
- package/skills/publish/assets/ci-contract.md +2 -2
- package/skills/publish/assets/ownership-matrix.md +1 -1
- package/skills/publish/scripts/finalize_changelog.sh +115 -0
- package/skills/publish/scripts/generate_release_notes.sh +475 -28
- package/skills/publish/scripts/npm_ci_pipeline.sh +44 -6
- package/skills/publish/scripts/publish_deploy.sh +38 -8
- package/skills/publish/scripts/run_gates.sh +2 -2
- package/skills/reconcile/SKILL.md +117 -5
- package/skills/reconcile/scripts/detect-unlinked-code.sh +741 -0
- package/skills/skillify/assets/init-new/assets/directory_structure.txt +5 -1
- package/skills/spinoff/SKILL.md +12 -7
- package/skills/todo/SKILL.md +2 -0
- package/skills/uncharted/SKILL.md +711 -0
- package/skills/uncharted/assets/SEGMENT_PROPOSAL_TEMPLATE.md +129 -0
- package/skills/uncharted/assets/UNDERSTANDING_DOC_TEMPLATE.md +160 -0
- package/skills/uncharted/scripts/apply-subsystem-cap.sh +573 -0
- package/skills/uncharted/scripts/detect-dependencies.sh +732 -0
- package/skills/uncharted/scripts/detect-tests.sh +553 -0
- package/skills/uncharted/scripts/discover-subsystems.sh +1029 -0
- package/skills/uncharted/scripts/enumerate-target.sh +470 -0
- package/skills/uncharted/scripts/import-source.sh +517 -0
- package/skills/uncharted/scripts/inspect-provenance.sh +573 -0
- package/skills/uncharted/scripts/resolve-segment-target.sh +640 -0
- package/skills/uncharted/scripts/run-engine.sh +655 -0
- package/skills/uncharted/scripts/validate-proposed-items.sh +125 -0
- package/skills/uncharted/scripts/write-backfilled-epics.sh +498 -0
- package/skills/wtf/SKILL.md +20 -0
- package/templates/CHANGELOG_TEMPLATE.md +13 -0
- package/templates/PROBLEM_RAPPORT_TEMPLATE.md +4 -1
- package/templates/SCRUM_BOARD_SCHEMA.md +206 -10
- package/templates/permission-levels/README.md +73 -0
- package/templates/permission-levels/level-1-locked.json +71 -0
- package/templates/permission-levels/level-2-guarded.json +64 -0
- package/templates/permission-levels/level-3-standard.json +62 -0
- package/templates/permission-levels/level-4-elevated.json +60 -0
- package/templates/permission-levels/level-5-unrestricted.json +58 -0
- package/skills/convert/SKILL.md +0 -124
- package/skills/convert/convert_cli.py +0 -235
- package/skills/convert/tests/sample.csv +0 -4
- package/skills/convert/tests/sample.json +0 -5
- package/skills/convert/tests/sample.jsonl +0 -3
- package/skills/convert/tests/sample.yaml +0 -18
- package/skills/convert/tests/sample_obj.csv +0 -2
- package/skills/convert/tests/sample_obj.json +0 -9
- package/skills/mirror-public/SKILL.md +0 -237
- package/skills/mirror-public/assets/config.json +0 -5
- package/skills/mirror-public/scripts/mirror.sh +0 -374
- package/skills/self-sync/SKILL.md +0 -73
- package/skills/self-sync/scripts/run.js +0 -136
- package/skills/train/SKILL.md +0 -116
- package/skills/train/assets/dashboard-templates/classifiers.html +0 -106
- package/skills/train/assets/dashboard-templates/nlp.html +0 -102
- package/skills/train/assets/dashboard-templates/transformers.html +0 -98
- package/skills/train/assets/results-parsers/__init__.py +0 -9
- package/skills/train/assets/results-parsers/classifiers.py +0 -84
- package/skills/train/assets/results-parsers/nlp.py +0 -88
- package/skills/train/assets/results-parsers/reporter.py +0 -154
- package/skills/train/assets/results-parsers/transformers.py +0 -120
- package/skills/train/train_cli.py +0 -786
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "training-runner-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server that validates and runs ML training jobs via bash start.sh",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node index.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@modelcontextprotocol/sdk": "^1.10.1",
|
|
12
|
+
"js-yaml": "^4.1.0",
|
|
13
|
+
"zod": "^3.23.8"
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jenga-ai/agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "JengaAgent — agentic project management CLI",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "https://github.com/samwelmunga/jenga-npm.git"
|
|
9
|
-
},
|
|
10
6
|
"publishConfig": {
|
|
11
7
|
"access": "public"
|
|
12
8
|
},
|
|
13
9
|
"bin": {
|
|
14
|
-
"jenga": "bin/jenga.js"
|
|
10
|
+
"jenga": "./bin/jenga.js"
|
|
15
11
|
},
|
|
16
12
|
"scripts": {
|
|
17
13
|
"postinstall": "node scripts/postinstall.js",
|
|
@@ -30,22 +26,24 @@
|
|
|
30
26
|
"templates/",
|
|
31
27
|
"bin/",
|
|
32
28
|
"lib/",
|
|
33
|
-
"mcp/
|
|
34
|
-
"mcp/help/",
|
|
35
|
-
"mcp/router/index.js",
|
|
36
|
-
"mcp/router/matcher.js",
|
|
37
|
-
"mcp/router/embedder.js",
|
|
38
|
-
"mcp/router/skill-index.js",
|
|
39
|
-
"mcp/router/package.json",
|
|
40
|
-
"mcp/router/package-lock.json",
|
|
41
|
-
"mcp/router/README.md",
|
|
29
|
+
"mcp/",
|
|
42
30
|
"README.md",
|
|
43
31
|
"LICENSE"
|
|
44
32
|
],
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/samwelmunga/jenga-npm"
|
|
36
|
+
},
|
|
45
37
|
"keywords": [],
|
|
46
38
|
"author": "",
|
|
47
39
|
"license": "MIT",
|
|
48
40
|
"dependencies": {
|
|
49
|
-
"@
|
|
41
|
+
"@huggingface/transformers": "^4.2.0"
|
|
42
|
+
},
|
|
43
|
+
"overrides": {
|
|
44
|
+
"sharp": "^0.34.5"
|
|
45
|
+
},
|
|
46
|
+
"comments": {
|
|
47
|
+
"audit": "sharp <0.35.0 and adm-zip <0.6.0 are transitive deps of @huggingface/transformers with no upstream fix available. Not exploitable in this context: only text feature-extraction pipeline is used — no image processing or ZIP handling at application boundary. Review when @huggingface/transformers ships a patched release."
|
|
50
48
|
}
|
|
51
49
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# check-permission-level.sh — read-only gate for a skill's declared minimum
|
|
4
|
+
# permission level (the `minimum_permission_level` SKILL.md frontmatter field,
|
|
5
|
+
# see docs/skill-authoring.md).
|
|
6
|
+
#
|
|
7
|
+
# ============================================================================
|
|
8
|
+
# CHECK vs ENFORCE — READ THIS BEFORE WIRING THIS SCRIPT INTO A SKILL
|
|
9
|
+
# ============================================================================
|
|
10
|
+
# This script is READ-ONLY. It CHECKS the current session's permission level
|
|
11
|
+
# against a required minimum and SIGNALS the result. It does NOT:
|
|
12
|
+
# - prompt the user for confirmation
|
|
13
|
+
# - elevate the session level
|
|
14
|
+
# - write to .jenga-permission-level.json, .claude/settings.json, or
|
|
15
|
+
# .agents/settings.json under any code path
|
|
16
|
+
#
|
|
17
|
+
# The CALLING SKILL's own instructions are responsible for:
|
|
18
|
+
# 1. Detecting the `NEEDS_CONFIRMATION` signal (non-zero exit) below.
|
|
19
|
+
# 2. Explicitly asking the user to confirm elevation (no silent
|
|
20
|
+
# auto-elevation, ever).
|
|
21
|
+
# 3. On confirmation, invoking `jenga-permission-level-switch.sh <minimum-level>`
|
|
22
|
+
# (a sibling script, scripts/jenga-permission-level-switch.sh — task E33_S02_T02) to
|
|
23
|
+
# actually perform the elevation.
|
|
24
|
+
# 4. Immediately after the skill's own work completes, resetting the
|
|
25
|
+
# session back to Guarded (2) — typically via `jenga-permission-level-switch.sh 2` as
|
|
26
|
+
# the skill's last step. This reset is NOT automatic and is NOT this
|
|
27
|
+
# script's job.
|
|
28
|
+
#
|
|
29
|
+
# Usage:
|
|
30
|
+
# scripts/check-permission-level.sh <minimum-level>
|
|
31
|
+
#
|
|
32
|
+
# <minimum-level> Integer 1-5 — the value a calling skill read from its
|
|
33
|
+
# own `minimum_permission_level` frontmatter field.
|
|
34
|
+
#
|
|
35
|
+
# Exit codes:
|
|
36
|
+
# 0 Current session level already meets <minimum-level>. No output.
|
|
37
|
+
# 1 Current session level is below <minimum-level>. Prints:
|
|
38
|
+
# NEEDS_CONFIRMATION: elevate from <current> to <minimum-level>
|
|
39
|
+
# 2 Bad usage / invalid argument. Prints an error to stderr.
|
|
40
|
+
#
|
|
41
|
+
# Current level source:
|
|
42
|
+
# .jenga-permission-level.json at the repo root, field `session_level`
|
|
43
|
+
# (the format `jenga-permission-level-switch.sh`, E33_S02_T02, writes: {"session_level": <n>}).
|
|
44
|
+
# Missing file, unreadable file, malformed JSON, or a missing/non-integer
|
|
45
|
+
# `session_level` field are all treated as level 2 (Guarded), the
|
|
46
|
+
# permanent default — this script never fails merely because the level
|
|
47
|
+
# file is absent or malformed.
|
|
48
|
+
# ============================================================================
|
|
49
|
+
|
|
50
|
+
set -euo pipefail
|
|
51
|
+
|
|
52
|
+
usage() {
|
|
53
|
+
echo "Usage: $(basename "$0") <minimum-level>" >&2
|
|
54
|
+
echo " <minimum-level> must be an integer 1-5 (the skill's minimum_permission_level)." >&2
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if [ "$#" -ne 1 ]; then
|
|
58
|
+
usage
|
|
59
|
+
exit 2
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
MIN_LEVEL="$1"
|
|
63
|
+
|
|
64
|
+
case "$MIN_LEVEL" in
|
|
65
|
+
1|2|3|4|5) ;;
|
|
66
|
+
*)
|
|
67
|
+
echo "Error: <minimum-level> must be an integer 1-5, got: '$MIN_LEVEL'" >&2
|
|
68
|
+
usage
|
|
69
|
+
exit 2
|
|
70
|
+
;;
|
|
71
|
+
esac
|
|
72
|
+
|
|
73
|
+
LEVEL_FILE=".jenga-permission-level.json"
|
|
74
|
+
CURRENT_LEVEL=2
|
|
75
|
+
|
|
76
|
+
if [ -f "$LEVEL_FILE" ]; then
|
|
77
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
78
|
+
PARSED=$(python3 -c "
|
|
79
|
+
import json, sys
|
|
80
|
+
try:
|
|
81
|
+
with open('$LEVEL_FILE') as f:
|
|
82
|
+
data = json.load(f)
|
|
83
|
+
level = data.get('session_level')
|
|
84
|
+
if isinstance(level, bool) or not isinstance(level, int) or not (1 <= level <= 5):
|
|
85
|
+
raise ValueError('invalid session_level')
|
|
86
|
+
print(level)
|
|
87
|
+
except Exception:
|
|
88
|
+
print(2)
|
|
89
|
+
" 2>/dev/null || echo 2)
|
|
90
|
+
CURRENT_LEVEL="$PARSED"
|
|
91
|
+
else
|
|
92
|
+
# No python3 available — fall back to a permissive grep-based extraction.
|
|
93
|
+
# Any failure to confidently parse an integer 1-5 falls back to level 2.
|
|
94
|
+
GREPPED=$(grep -o '"session_level"[[:space:]]*:[[:space:]]*[0-9]' "$LEVEL_FILE" 2>/dev/null | grep -o '[0-9]$' | head -n1 || true)
|
|
95
|
+
case "$GREPPED" in
|
|
96
|
+
1|2|3|4|5) CURRENT_LEVEL="$GREPPED" ;;
|
|
97
|
+
*) CURRENT_LEVEL=2 ;;
|
|
98
|
+
esac
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
if [ "$CURRENT_LEVEL" -ge "$MIN_LEVEL" ]; then
|
|
103
|
+
exit 0
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
echo "NEEDS_CONFIRMATION: elevate from $CURRENT_LEVEL to $MIN_LEVEL"
|
|
107
|
+
exit 1
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ---------------------------------------------------------------------------
|
|
3
|
+
# scripts/check-publicignore-match.sh
|
|
4
|
+
#
|
|
5
|
+
# Classify one or more repo-relative paths as PUBLIC (would ship to the
|
|
6
|
+
# public mirror repo) or BLOCKED (excluded by .publicignore), using the
|
|
7
|
+
# exact same rsync --exclude-from=.publicignore matching semantics as
|
|
8
|
+
# skills/mirror-public/scripts/mirror.sh's --dry-run "ship list" computation
|
|
9
|
+
# (see the SHIP_LIST_FILE block in that script). A file classified as
|
|
10
|
+
# "would be blocked" by `/mirror-public --dry-run` is guaranteed to be
|
|
11
|
+
# classified as BLOCKED here too, and vice versa for PUBLIC.
|
|
12
|
+
#
|
|
13
|
+
# This does NOT touch the network, clone the public repo, or require
|
|
14
|
+
# /mirror-public to be configured (skills/mirror-public/assets/config.json
|
|
15
|
+
# is never read) — it only needs a .publicignore file at the repo root.
|
|
16
|
+
# Rsync's include/exclude filter evaluation does not depend on destination
|
|
17
|
+
# state (destination state only affects delete/itemize-flag details for
|
|
18
|
+
# files already present there), so probing against an empty scratch temp
|
|
19
|
+
# directory yields an identical "would ship" set to probing against
|
|
20
|
+
# mirror.sh's real public-clone destination.
|
|
21
|
+
#
|
|
22
|
+
# Callers (e.g. /doc-sync step 4, and the E36 changelog generator's
|
|
23
|
+
# .publicignore-awareness) are expected to skip invoking this script
|
|
24
|
+
# entirely when .publicignore does not exist at the repo root — that is
|
|
25
|
+
# the established no-op-when-absent precedent, and it stays owned by each
|
|
26
|
+
# caller rather than being silently absorbed here.
|
|
27
|
+
#
|
|
28
|
+
# Usage:
|
|
29
|
+
# check-publicignore-match.sh <path> [<path> ...]
|
|
30
|
+
#
|
|
31
|
+
# Paths are interpreted relative to the repo root (same convention as
|
|
32
|
+
# `git ls-files`). Output: one "STATUS<TAB>path" line per input path, in
|
|
33
|
+
# input order. STATUS is PUBLIC or BLOCKED.
|
|
34
|
+
#
|
|
35
|
+
# Note: a path that does not exist on disk is classified BLOCKED (it will
|
|
36
|
+
# not appear in rsync's transfer list regardless of .publicignore). Callers
|
|
37
|
+
# such as /doc-sync only ever pass paths already confirmed to exist as
|
|
38
|
+
# real new-in-source candidates, so this does not arise in practice — but
|
|
39
|
+
# it is not the same guarantee as ".publicignore explicitly matched it".
|
|
40
|
+
#
|
|
41
|
+
# Exit codes:
|
|
42
|
+
# 0 classification completed (regardless of individual PUBLIC/BLOCKED results)
|
|
43
|
+
# 1 usage error
|
|
44
|
+
# 2 repo root not found, .publicignore missing, or rsync unavailable
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
set -euo pipefail
|
|
48
|
+
IFS=$'\n\t'
|
|
49
|
+
|
|
50
|
+
die() {
|
|
51
|
+
printf 'check-publicignore-match.sh: error: %s\n' "$*" >&2
|
|
52
|
+
exit 2
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if [ "$#" -eq 0 ]; then
|
|
56
|
+
printf 'usage: check-publicignore-match.sh <path> [<path> ...]\n' >&2
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
SCRIPT_PATH="${BASH_SOURCE[0]}"
|
|
61
|
+
while [ -h "$SCRIPT_PATH" ]; do
|
|
62
|
+
LINK_TARGET="$(readlink "$SCRIPT_PATH")"
|
|
63
|
+
case "$LINK_TARGET" in
|
|
64
|
+
/*) SCRIPT_PATH="$LINK_TARGET" ;;
|
|
65
|
+
*) SCRIPT_PATH="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)/$LINK_TARGET" ;;
|
|
66
|
+
esac
|
|
67
|
+
done
|
|
68
|
+
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
69
|
+
|
|
70
|
+
REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
71
|
+
[ -n "$REPO_ROOT" ] || die "could not locate repo root (git rev-parse failed from $SCRIPT_DIR)"
|
|
72
|
+
|
|
73
|
+
PUBLICIGNORE="$REPO_ROOT/.publicignore"
|
|
74
|
+
[ -f "$PUBLICIGNORE" ] || die "no .publicignore at $PUBLICIGNORE (caller should skip invoking this script when absent — that is the no-op case, not an error the caller should surface)"
|
|
75
|
+
|
|
76
|
+
command -v rsync >/dev/null 2>&1 || die "rsync not installed"
|
|
77
|
+
|
|
78
|
+
TMP_DEST="$(mktemp -d)"
|
|
79
|
+
SHIP_LIST_FILE="$(mktemp)"
|
|
80
|
+
cleanup() {
|
|
81
|
+
rm -rf "$TMP_DEST"
|
|
82
|
+
rm -f "$SHIP_LIST_FILE"
|
|
83
|
+
}
|
|
84
|
+
trap cleanup EXIT
|
|
85
|
+
|
|
86
|
+
# Same rsync flags + itemize/awk filter as mirror.sh's --dry-run ship-list
|
|
87
|
+
# computation (kept in lockstep with that block intentionally), targeted at
|
|
88
|
+
# an empty scratch dir instead of a real public clone.
|
|
89
|
+
rsync -a --delete \
|
|
90
|
+
--exclude-from="$PUBLICIGNORE" \
|
|
91
|
+
--exclude=".git" \
|
|
92
|
+
--dry-run --itemize-changes --out-format='%i %n' \
|
|
93
|
+
"$REPO_ROOT/" \
|
|
94
|
+
"$TMP_DEST/" \
|
|
95
|
+
| awk '{
|
|
96
|
+
flag = $1;
|
|
97
|
+
first = substr(flag, 1, 1);
|
|
98
|
+
second = substr(flag, 2, 1);
|
|
99
|
+
# Skip directories — public tree recreates them implicitly.
|
|
100
|
+
if (second == "d") next;
|
|
101
|
+
# Keep only entries that transfer or create a file/symlink/hardlink.
|
|
102
|
+
keep = 0;
|
|
103
|
+
if (first == ">" || first == "<") keep = 1; # file transfer
|
|
104
|
+
else if (first == "c" && second == "L") keep = 1; # create symlink
|
|
105
|
+
else if (first == "h") keep = 1; # hard link
|
|
106
|
+
if (!keep) next;
|
|
107
|
+
$1 = "";
|
|
108
|
+
sub(/^ /, "");
|
|
109
|
+
sub(/\/$/, "");
|
|
110
|
+
if (length($0) > 0) print $0;
|
|
111
|
+
}' \
|
|
112
|
+
| LC_ALL=C sort -u > "$SHIP_LIST_FILE"
|
|
113
|
+
|
|
114
|
+
for candidate in "$@"; do
|
|
115
|
+
# Normalize a single leading "./" if present, so callers can pass either form.
|
|
116
|
+
norm="${candidate#./}"
|
|
117
|
+
if LC_ALL=C grep -Fxq "$norm" "$SHIP_LIST_FILE"; then
|
|
118
|
+
printf 'PUBLIC\t%s\n' "$candidate"
|
|
119
|
+
else
|
|
120
|
+
printf 'BLOCKED\t%s\n' "$candidate"
|
|
121
|
+
fi
|
|
122
|
+
done
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/check-worktree-liveness.sh — detect OS processes still rooted
|
|
3
|
+
# inside a given worktree path.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# scripts/check-worktree-liveness.sh <worktree-path>
|
|
7
|
+
#
|
|
8
|
+
# Why this exists: `ListAgents` reports agent lifecycle, not detached OS
|
|
9
|
+
# processes. A live incident (2026-08-25, see E37's Purpose) showed a
|
|
10
|
+
# completed agent leaving shell polling loops running with their cwd inside
|
|
11
|
+
# a worktree; `ListAgents` showed the agent itself as "completed" and gave
|
|
12
|
+
# no signal that those loops were still alive, so the worktree was removed
|
|
13
|
+
# out from under them. This script answers a narrower, deterministic
|
|
14
|
+
# question instead: "does any OS process currently have its *current
|
|
15
|
+
# working directory* rooted inside this path (the path itself, or any
|
|
16
|
+
# subdirectory of it)?" It is intended to be run as a precondition before
|
|
17
|
+
# `git worktree remove` (wired in the sibling task E37_S03_T02, not here —
|
|
18
|
+
# this task only produces the standalone check).
|
|
19
|
+
#
|
|
20
|
+
# Detection mechanism (primary): `lsof +D <path> -F pcfn`
|
|
21
|
+
# `+D <path>` recursively restricts lsof's report to files open within
|
|
22
|
+
# that directory subtree (this alone gives us "inside the given path,
|
|
23
|
+
# including subdirectories" — verified locally against a nested-subdir
|
|
24
|
+
# case). `-F pcfn` is lsof's machine-parsable output mode, emitting one
|
|
25
|
+
# attribute per line: `p<pid>`, `c<command>`, `f<fd-or-descriptor-type>`,
|
|
26
|
+
# `n<file name>`, grouped per matching open-file record. Records are then
|
|
27
|
+
# filtered down to those whose `f` field is literally `cwd` — this is what
|
|
28
|
+
# distinguishes "this process's current working directory is here" from
|
|
29
|
+
# "this process merely has some unrelated file open under here" (e.g. a
|
|
30
|
+
# log file), which `+D` alone does not distinguish.
|
|
31
|
+
#
|
|
32
|
+
# `-F pcfn` (not lsof's default human-readable columnar output) is used
|
|
33
|
+
# deliberately: column widths/ordering in the default output are not
|
|
34
|
+
# guaranteed stable across lsof versions/platforms, whereas the `-F` field
|
|
35
|
+
# protocol is. Verified directly against this repo's own Darwin runtime
|
|
36
|
+
# (lsof 4.91) — both an exact-path cwd match and a subdirectory cwd match
|
|
37
|
+
# are correctly reported, and process cwd is resolved through symlinks
|
|
38
|
+
# (macOS reports `/tmp/x` as `/private/tmp/x`), which is why the input
|
|
39
|
+
# path is canonicalized below before comparison.
|
|
40
|
+
#
|
|
41
|
+
# Detection mechanism (fallback, when `lsof` is not on PATH): enumerate
|
|
42
|
+
# `/proc/[0-9]*` (Linux only — macOS has no `/proc`) and `readlink` each
|
|
43
|
+
# `/proc/<pid>/cwd`, comparing against the canonicalized target path. macOS
|
|
44
|
+
# ships `lsof` by default, so this branch is not expected to run on this
|
|
45
|
+
# repo's actual runtime, but keeps the script functional on minimal Linux
|
|
46
|
+
# containers that may lack `lsof`. If neither mechanism is available, the
|
|
47
|
+
# script cannot determine liveness at all and fails closed (non-zero exit,
|
|
48
|
+
# clear stderr message) rather than silently reporting "clear" when it does
|
|
49
|
+
# not actually know.
|
|
50
|
+
#
|
|
51
|
+
# Command-line resolution for reported matches uses `ps -o command= -p
|
|
52
|
+
# <pid>` — confirmed to behave identically on macOS's BSD `ps` and Linux's
|
|
53
|
+
# GNU `ps`, unlike some other `ps` flags/keywords (e.g. `-e` listing
|
|
54
|
+
# semantics differ between BSD and GNU `ps`, which is why the fallback path
|
|
55
|
+
# above enumerates PIDs via `/proc` rather than `ps -e`).
|
|
56
|
+
#
|
|
57
|
+
# Caller-cwd self-detection hardening: if the *caller* invokes this script
|
|
58
|
+
# from a shell whose own cwd is already inside the target path (e.g. `cd
|
|
59
|
+
# <worktree> && check-worktree-liveness.sh <worktree>` — a very plausible
|
|
60
|
+
# state right before a worktree is removed), every subprocess the script
|
|
61
|
+
# forks (the `lsof` child, and the process-substitution subshell wrapping
|
|
62
|
+
# it below, or the `readlink` calls in the /proc fallback) would otherwise
|
|
63
|
+
# *inherit* that cwd, since bash processes inherit cwd from their parent by
|
|
64
|
+
# default. That produced a real, reproduced false positive: the script's
|
|
65
|
+
# own subprocess chain got reported as a "live process rooted in" the very
|
|
66
|
+
# path being checked, even with zero genuinely orphaned processes running.
|
|
67
|
+
# See project/rapports/problems/E37_S03_T01-self-detection-false-positive-remarks.md
|
|
68
|
+
# for the original reproduction and the two fix options it proposed.
|
|
69
|
+
#
|
|
70
|
+
# Fix: immediately after resolving $ABS_PATH (and before spawning anything),
|
|
71
|
+
# the script `cd`s itself to `/` — a path guaranteed to be outside every
|
|
72
|
+
# possible target. This moves the *script's own process* outside the target,
|
|
73
|
+
# so every descendant it forks from this point on inherits a cwd of `/`
|
|
74
|
+
# instead of the caller's original cwd, regardless of where the caller
|
|
75
|
+
# happened to be standing when it invoked this script. This is preferred
|
|
76
|
+
# over tracking and excluding individual subprocess PIDs (the script's own
|
|
77
|
+
# PID `$$` was already excluded as a courtesy, see below) because `cd /`
|
|
78
|
+
# removes the precondition for the whole bug class structurally, rather
|
|
79
|
+
# than requiring every current and future subprocess this script might ever
|
|
80
|
+
# spawn to be enumerated and excluded by PID after the fact.
|
|
81
|
+
#
|
|
82
|
+
# The script also excludes its own PID ($$) from reported matches as a
|
|
83
|
+
# second, defense-in-depth courtesy (now largely redundant given the `cd /`
|
|
84
|
+
# fix above, but harmless to keep).
|
|
85
|
+
#
|
|
86
|
+
# Exit codes:
|
|
87
|
+
# 0 clear — no live process found, no output
|
|
88
|
+
# 1 one or more live processes found — PID(s) and command(s) printed
|
|
89
|
+
# 2 invalid usage (wrong argument count / empty argument)
|
|
90
|
+
# 3 <worktree-path> does not resolve to an existing directory
|
|
91
|
+
# 4 unable to determine liveness on this platform (neither `lsof` nor
|
|
92
|
+
# /proc available) — fail closed, this is NOT the same as "clear"
|
|
93
|
+
|
|
94
|
+
set -u
|
|
95
|
+
|
|
96
|
+
SCRIPT_NAME="check-worktree-liveness.sh"
|
|
97
|
+
|
|
98
|
+
usage() {
|
|
99
|
+
echo "Usage: $SCRIPT_NAME <worktree-path>" >&2
|
|
100
|
+
exit 2
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
[ "$#" -eq 1 ] || usage
|
|
104
|
+
RAW_PATH="$1"
|
|
105
|
+
[ -n "$RAW_PATH" ] || usage
|
|
106
|
+
|
|
107
|
+
if [ ! -d "$RAW_PATH" ]; then
|
|
108
|
+
echo "$SCRIPT_NAME: '$RAW_PATH' is not a directory" >&2
|
|
109
|
+
exit 3
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Canonicalize to an absolute, symlink-resolved path so comparisons against
|
|
113
|
+
# process cwd values (which the OS reports fully resolved — e.g. macOS
|
|
114
|
+
# reports /tmp as /private/tmp) match correctly regardless of how the
|
|
115
|
+
# caller spelled the input path.
|
|
116
|
+
ABS_PATH="$(cd "$RAW_PATH" 2>/dev/null && pwd -P)"
|
|
117
|
+
if [ -z "$ABS_PATH" ]; then
|
|
118
|
+
echo "$SCRIPT_NAME: unable to resolve '$RAW_PATH' to an absolute path" >&2
|
|
119
|
+
exit 3
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Move this process (and therefore every subprocess it forks from here on)
|
|
123
|
+
# outside of every possible target path, so the scan below can never
|
|
124
|
+
# self-detect regardless of the caller's cwd at invocation time. See the
|
|
125
|
+
# "Caller-cwd self-detection hardening" comment in the header above. `/` is
|
|
126
|
+
# always present and readable on every platform this script targets; if for
|
|
127
|
+
# some reason it could not be `cd`'d into, fall through rather than abort —
|
|
128
|
+
# worst case the pre-existing $$ exclusion below is the only mitigation,
|
|
129
|
+
# same as before this fix.
|
|
130
|
+
cd / 2>/dev/null || true
|
|
131
|
+
|
|
132
|
+
SELF_PID="$$"
|
|
133
|
+
FOUND_ANY=0
|
|
134
|
+
REPORTED_PIDS=""
|
|
135
|
+
|
|
136
|
+
already_reported() {
|
|
137
|
+
case " $REPORTED_PIDS " in
|
|
138
|
+
*" $1 "*) return 0 ;;
|
|
139
|
+
*) return 1 ;;
|
|
140
|
+
esac
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
report_match() {
|
|
144
|
+
pid="$1"
|
|
145
|
+
already_reported "$pid" && return 0
|
|
146
|
+
REPORTED_PIDS="$REPORTED_PIDS $pid"
|
|
147
|
+
cmd="$(ps -o command= -p "$pid" 2>/dev/null)"
|
|
148
|
+
[ -n "$cmd" ] || cmd="(command unavailable — process may have exited)"
|
|
149
|
+
echo "$SCRIPT_NAME: live process rooted in '$ABS_PATH' — PID $pid: $cmd"
|
|
150
|
+
FOUND_ANY=1
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
154
|
+
current_pid=""
|
|
155
|
+
current_fd=""
|
|
156
|
+
while IFS= read -r line; do
|
|
157
|
+
case "$line" in
|
|
158
|
+
p*)
|
|
159
|
+
current_pid="${line#p}"
|
|
160
|
+
current_fd=""
|
|
161
|
+
;;
|
|
162
|
+
f*)
|
|
163
|
+
current_fd="${line#f}"
|
|
164
|
+
;;
|
|
165
|
+
n*)
|
|
166
|
+
if [ "$current_fd" = "cwd" ] && [ -n "$current_pid" ] && [ "$current_pid" != "$SELF_PID" ]; then
|
|
167
|
+
report_match "$current_pid"
|
|
168
|
+
fi
|
|
169
|
+
;;
|
|
170
|
+
esac
|
|
171
|
+
done < <(lsof +D "$ABS_PATH" -F pcfn 2>/dev/null)
|
|
172
|
+
else
|
|
173
|
+
if [ -d /proc ]; then
|
|
174
|
+
for pid_dir in /proc/[0-9]*; do
|
|
175
|
+
[ -e "$pid_dir" ] || continue
|
|
176
|
+
pid="${pid_dir#/proc/}"
|
|
177
|
+
[ "$pid" != "$SELF_PID" ] || continue
|
|
178
|
+
cwd_link="$(readlink "$pid_dir/cwd" 2>/dev/null)" || continue
|
|
179
|
+
[ -n "$cwd_link" ] || continue
|
|
180
|
+
case "$cwd_link" in
|
|
181
|
+
"$ABS_PATH"|"$ABS_PATH"/*)
|
|
182
|
+
report_match "$pid"
|
|
183
|
+
;;
|
|
184
|
+
esac
|
|
185
|
+
done
|
|
186
|
+
else
|
|
187
|
+
echo "$SCRIPT_NAME: neither 'lsof' nor /proc is available on this system — cannot determine worktree liveness for '$ABS_PATH'" >&2
|
|
188
|
+
exit 4
|
|
189
|
+
fi
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
[ "$FOUND_ANY" -eq 1 ] && exit 1
|
|
193
|
+
exit 0
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scripts/generate-rapport-manifest.sh
|
|
3
|
+
#
|
|
4
|
+
# Regenerates the rapport manifest used by hooks/on_session_end.sh to detect
|
|
5
|
+
# *new* problem rapports (project/rapports/problems/*.md, excluding resolved
|
|
6
|
+
# *.IGNORE.md files). This script is the single source of truth for "what
|
|
7
|
+
# does the manifest look like right now" — it is used both to produce the
|
|
8
|
+
# committed seed manifest and, at runtime, by the hook itself whenever it
|
|
9
|
+
# rewrites the manifest after finding new rapports.
|
|
10
|
+
#
|
|
11
|
+
# Entries are stored as paths relative to the repo root (e.g.
|
|
12
|
+
# "project/rapports/problems/foo.md"), NOT absolute paths. The manifest is
|
|
13
|
+
# a committed artifact shared across every clone/worktree, each of which
|
|
14
|
+
# has a different absolute checkout location — absolute paths would make
|
|
15
|
+
# the seed match nobody's filesystem but the one it was generated on.
|
|
16
|
+
#
|
|
17
|
+
# Safe to re-run at any time: it is a pure snapshot of the current directory
|
|
18
|
+
# contents, never a diff, so running it twice in a row produces identical
|
|
19
|
+
# output.
|
|
20
|
+
#
|
|
21
|
+
# Usage: scripts/generate-rapport-manifest.sh [output_file]
|
|
22
|
+
# output_file defaults to project/data/rapport_manifest.json
|
|
23
|
+
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
# shellcheck source=lib/resolve-project-dir.sh
|
|
27
|
+
source "$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh"
|
|
28
|
+
|
|
29
|
+
PROJECT_DIR="$JENGA_PROJECT_DIR"
|
|
30
|
+
RAPPORT_DIR="$PROJECT_DIR/project/rapports/problems"
|
|
31
|
+
OUTPUT="${1:-$PROJECT_DIR/project/data/rapport_manifest.json}"
|
|
32
|
+
|
|
33
|
+
mkdir -p "$(dirname "$OUTPUT")"
|
|
34
|
+
|
|
35
|
+
if [ -d "$RAPPORT_DIR" ]; then
|
|
36
|
+
find "$RAPPORT_DIR" -name "*.md" ! -name "*.IGNORE.md" 2>/dev/null \
|
|
37
|
+
| sed "s|^$PROJECT_DIR/||" \
|
|
38
|
+
| sort | jq -R . | jq -s . > "$OUTPUT"
|
|
39
|
+
else
|
|
40
|
+
echo "[]" > "$OUTPUT"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
echo "Wrote $(jq 'length' "$OUTPUT") rapport filename(s) to $OUTPUT"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/idea_manager.sh — canonical owner of all project/ideas.md operations.
|
|
3
|
+
# Run from the repository root.
|
|
4
|
+
|
|
5
|
+
IDEA_FILE="project/ideas.md"
|
|
6
|
+
TEMPLATE="skills/idea/assets/idea_template.md"
|
|
7
|
+
|
|
8
|
+
usage() {
|
|
9
|
+
cat >&2 <<EOF
|
|
10
|
+
Usage: $0 <subcommand> [args]
|
|
11
|
+
|
|
12
|
+
Subcommands:
|
|
13
|
+
add "<entry>" Append entry to project/ideas.md (auto-creates from template if missing)
|
|
14
|
+
list Print all non-comment, non-blank entries (silent if file missing/empty)
|
|
15
|
+
EOF
|
|
16
|
+
exit 1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Filters out blank lines, lines starting with #, and HTML comments
|
|
20
|
+
real_entries() {
|
|
21
|
+
grep -v '^\s*$' "$1" 2>/dev/null \
|
|
22
|
+
| grep -v '^\s*#' \
|
|
23
|
+
| grep -v '^\s*<!--'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
case "${1:-}" in
|
|
27
|
+
add)
|
|
28
|
+
[ -z "${2:-}" ] && { echo "Error: add requires an entry argument" >&2; exit 1; }
|
|
29
|
+
if [ ! -f "$IDEA_FILE" ]; then
|
|
30
|
+
if [ ! -f "$TEMPLATE" ]; then
|
|
31
|
+
echo "Error: template not found at $TEMPLATE" >&2; exit 1
|
|
32
|
+
fi
|
|
33
|
+
cp "$TEMPLATE" "$IDEA_FILE"
|
|
34
|
+
fi
|
|
35
|
+
printf '%s\n' "$2" >> "$IDEA_FILE"
|
|
36
|
+
;;
|
|
37
|
+
|
|
38
|
+
list)
|
|
39
|
+
[ ! -f "$IDEA_FILE" ] && exit 0
|
|
40
|
+
real_entries "$IDEA_FILE"
|
|
41
|
+
exit 0
|
|
42
|
+
;;
|
|
43
|
+
|
|
44
|
+
*)
|
|
45
|
+
usage
|
|
46
|
+
;;
|
|
47
|
+
esac
|