@svayam-opensource/prj 0.5.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.
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env bash
2
+ # Script: propose-knowledge
3
+ # Purpose: Ad-hoc org knowledge proposals outside any project context.
4
+ # Used for initial bootstrap, policy updates, and standalone learnings.
5
+ # Usage: bash propose-knowledge.sh <branch_slug> <description>
6
+ # Compliance: C02 — proposals require domain owner approval via PR (POL-107 to POL-109)
7
+
8
+ set -euo pipefail
9
+ source "$(dirname "$0")/lib.sh"
10
+ load_config
11
+
12
+ # ── Inputs ────────────────────────────────────────────────────────────────────
13
+
14
+ BRANCH_SLUG="${1:-}"
15
+ DESCRIPTION="${2:-}"
16
+ MODE="${3:-create}" # create (default), --submit, --archive
17
+
18
+ [[ -n "$BRANCH_SLUG" ]] || hard_stop "Usage: $0 <branch_slug> <description> [--submit|--archive]"
19
+ [[ -n "$DESCRIPTION" ]] || hard_stop "Usage: $0 <branch_slug> <description> [--submit|--archive]"
20
+
21
+ case "$MODE" in
22
+ create|--submit|--archive) ;;
23
+ *) hard_stop "Unknown mode: $MODE (expected --submit or --archive)" ;;
24
+ esac
25
+
26
+ KNOWLEDGE_BRANCH="knowledge-$BRANCH_SLUG"
27
+
28
+ echo "=== propose-knowledge"
29
+ echo " Branch: $KNOWLEDGE_BRANCH"
30
+ echo " Description: $DESCRIPTION"
31
+ echo " Mode: $MODE"
32
+ echo ""
33
+
34
+ cd "$REPO_ROOT"
35
+ git fetch origin "$DEFAULT_BRANCH" 2>/dev/null || true
36
+ git fetch origin "$KNOWLEDGE_BRANCH" 2>/dev/null || true
37
+
38
+ # ── Mode: create ──────────────────────────────────────────────────────────────
39
+
40
+ if [[ "$MODE" == "create" ]]; then
41
+ if git rev-parse --verify "$KNOWLEDGE_BRANCH" &>/dev/null || \
42
+ git ls-remote --exit-code origin "$KNOWLEDGE_BRANCH" &>/dev/null; then
43
+ hard_stop "Branch '$KNOWLEDGE_BRANCH' already exists — investigate before proceeding."
44
+ fi
45
+
46
+ # Failure cleanup (#64): if branch creation/push fails part-way (e.g. push
47
+ # rejected after the local branch exists), undo what we made so a re-run does
48
+ # not hard-stop on "branch already exists". State flags gate each undo; the
49
+ # trap is disarmed once create mode completes successfully.
50
+ _PK_BRANCH_CREATED=false
51
+ _PK_BRANCH_PUSHED=false
52
+ _PK_DONE=false
53
+ cleanup_on_failure() {
54
+ local rc=$?
55
+ $_PK_DONE && return 0
56
+ [[ $rc -eq 0 ]] && return 0
57
+ warn "propose-knowledge failed (exit $rc) — cleaning up so the run is re-runnable."
58
+ if $_PK_BRANCH_CREATED; then
59
+ git -C "$REPO_ROOT" checkout "$DEFAULT_BRANCH" &>/dev/null || true
60
+ local scope="local"
61
+ if $_PK_BRANCH_PUSHED; then
62
+ git -C "$REPO_ROOT" push origin --delete "$KNOWLEDGE_BRANCH" &>/dev/null || true
63
+ scope="local + remote"
64
+ fi
65
+ git -C "$REPO_ROOT" branch -D "$KNOWLEDGE_BRANCH" &>/dev/null || true
66
+ info "Removed branch '$KNOWLEDGE_BRANCH' ($scope)."
67
+ fi
68
+ }
69
+ trap cleanup_on_failure EXIT
70
+
71
+ git checkout "$DEFAULT_BRANCH"
72
+ git pull origin "$DEFAULT_BRANCH"
73
+ git checkout -b "$KNOWLEDGE_BRANCH"
74
+ _PK_BRANCH_CREATED=true
75
+ git push -u origin "$KNOWLEDGE_BRANCH"
76
+ _PK_BRANCH_PUSHED=true
77
+
78
+ echo "Branch '$KNOWLEDGE_BRANCH' created."
79
+ echo ""
80
+ echo "=== Next: Author your knowledge changes"
81
+ echo ""
82
+ echo " 1. Edit or add files under knowledge/ on branch '$KNOWLEDGE_BRANCH'."
83
+ echo " - New content: add to the owning domain's layer (see knowledge/README.md)"
84
+ echo " - Policy updates: edit knowledge/policies/"
85
+ echo " - Patterns: edit <domain>/patterns/"
86
+ echo " - Architecture: edit knowledge/architecture/{system,data}/"
87
+ echo ""
88
+ echo " 2. Commit your changes:"
89
+ echo " git add knowledge/"
90
+ echo " git commit -m 'knowledge: $DESCRIPTION'"
91
+ echo " git push origin $KNOWLEDGE_BRANCH"
92
+ echo ""
93
+ echo " 3. Then create the PR:"
94
+ echo " bash propose-knowledge.sh $BRANCH_SLUG \"$DESCRIPTION\" --submit"
95
+ echo ""
96
+ # Success: branch is intentionally kept for the author — disarm cleanup.
97
+ _PK_DONE=true
98
+ exit 0
99
+ fi
100
+
101
+ # ── Mode: --submit / --archive — branch must already exist ───────────────────
102
+
103
+ if ! git rev-parse --verify "$KNOWLEDGE_BRANCH" &>/dev/null && \
104
+ ! git ls-remote --exit-code origin "$KNOWLEDGE_BRANCH" &>/dev/null; then
105
+ hard_stop "Branch '$KNOWLEDGE_BRANCH' does not exist. Run without --submit/--archive first to create it."
106
+ fi
107
+
108
+ if [[ "$MODE" == "--submit" ]]; then
109
+ # ── Raise PR ────────────────────────────────────────────────────────────────
110
+ echo "Creating PR: $KNOWLEDGE_BRANCH → $DEFAULT_BRANCH..."
111
+
112
+ PR_BODY=$(cat <<MD
113
+ ## Knowledge Proposal: $DESCRIPTION
114
+
115
+ **Branch:** \`$KNOWLEDGE_BRANCH\`
116
+ **Author:** $(git config user.email 2>/dev/null || echo "unknown")
117
+ **Date:** $(today)
118
+
119
+ ### Summary
120
+
121
+ $DESCRIPTION
122
+
123
+ ### Review Notes
124
+
125
+ - This is a manually authored knowledge proposal (no LLM synthesis).
126
+ - CODEOWNERS will auto-assign domain owners as reviewers based on folders touched.
127
+ - Once merged, CI/CD will regenerate static site and vector embeddings.
128
+
129
+ ### Post-merge
130
+
131
+ Archive tag \`archive/$KNOWLEDGE_BRANCH\` will be created and branch deleted.
132
+
133
+ *Generated by propose-knowledge.sh*
134
+ MD
135
+ )
136
+
137
+ PR_URL=$(gh pr create \
138
+ --base "$DEFAULT_BRANCH" \
139
+ --head "$KNOWLEDGE_BRANCH" \
140
+ --title "[Knowledge Proposal] $DESCRIPTION" \
141
+ --body "$PR_BODY" \
142
+ 2>/dev/null) \
143
+ || {
144
+ warn "PR creation failed — retrying..."
145
+ PR_URL=$(gh pr create \
146
+ --base "$DEFAULT_BRANCH" \
147
+ --head "$KNOWLEDGE_BRANCH" \
148
+ --title "[Knowledge Proposal] $DESCRIPTION" \
149
+ --body "$PR_BODY")
150
+ }
151
+
152
+ echo ""
153
+ echo "=== PR created: $PR_URL"
154
+ echo " CODEOWNERS will auto-assign domain reviewers."
155
+ echo ""
156
+ echo " After merge, archive the branch:"
157
+ echo " bash propose-knowledge.sh $BRANCH_SLUG \"$DESCRIPTION\" --archive"
158
+ fi
159
+
160
+ if [[ "$MODE" == "--archive" ]]; then
161
+ # ── Archive after merge ──────────────────────────────────────────────────────
162
+ git fetch origin "$DEFAULT_BRANCH"
163
+ git checkout "$DEFAULT_BRANCH"
164
+ git pull origin "$DEFAULT_BRANCH"
165
+ archive_branch "$REPO_ROOT" "$KNOWLEDGE_BRANCH"
166
+ echo ""
167
+ echo "=== Branch archived: archive/$KNOWLEDGE_BRANCH"
168
+ fi
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/release-to-public.sh
3
+ #
4
+ # Cuts a new release of the Agentic Development Framework from the publish
5
+ # branch. The release process is:
6
+ #
7
+ # 1. Run the end-to-end smoke test (gate — non-bypassable except by
8
+ # explicit confirmation).
9
+ # 2. Verify we're on publish, clean, and up to date with origin.
10
+ # 3. Tag publish at HEAD with the requested version.
11
+ # 4. Push the tag to origin (this updates the public template repo's
12
+ # releases page).
13
+ #
14
+ # Usage:
15
+ # bash scripts/release-to-public.sh vX.Y.Z
16
+ # bash scripts/release-to-public.sh --skip-smoke vX.Y.Z # emergency only
17
+ #
18
+ # Run this from the publish branch. The script will refuse to run from
19
+ # any other branch.
20
+ #
21
+ # After this script succeeds, the maintainer still needs to:
22
+ # - sync publish → main (bash scripts/sync-from-publish.sh)
23
+ # - push publish to the public template repo at
24
+ # svayam-opensource/governed-agentic-dev-framework
25
+
26
+ set -uo pipefail
27
+
28
+ # ── Output helpers (self-contained — we don't source lib.sh because on the
29
+ # publish branch, org-config.yaml ships with empty values which would trip
30
+ # lib.sh's load_config) ──────────────────────────────────────────────────────
31
+
32
+ BOLD='\033[1m'; CYAN='\033[0;36m'; GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m'
33
+ info() { echo -e "${CYAN} →${NC} $*"; }
34
+ ok() { echo -e "${GREEN} ✓${NC} $*"; }
35
+ warn() { echo -e "${YELLOW} !${NC} $*" >&2; }
36
+ err() { echo -e "${RED} ✗${NC} $*" >&2; }
37
+ header() { echo ""; echo -e "${BOLD}${CYAN}$*${NC}"; }
38
+ hard_stop() { err "$*"; exit 1; }
39
+
40
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
41
+
42
+ # ── Flag parsing ──────────────────────────────────────────────────────────────
43
+
44
+ SKIP_SMOKE=false
45
+ VERSION=""
46
+
47
+ while [[ $# -gt 0 ]]; do
48
+ case "$1" in
49
+ --skip-smoke) SKIP_SMOKE=true ;;
50
+ -h|--help)
51
+ grep '^# ' "$0" | sed 's/^# //;s/^#//' | head -30
52
+ exit 0
53
+ ;;
54
+ v*.*.*) VERSION="$1" ;;
55
+ *) hard_stop "Unknown arg: $1 (expected vX.Y.Z or --skip-smoke)" ;;
56
+ esac
57
+ shift
58
+ done
59
+
60
+ [[ -n "$VERSION" ]] || hard_stop "Missing version. Usage: bash $0 vX.Y.Z"
61
+
62
+ # Validate semver shape
63
+ [[ "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] \
64
+ || hard_stop "Version '$VERSION' is not a vX.Y.Z semver tag."
65
+
66
+ # ── Pre-conditions ────────────────────────────────────────────────────────────
67
+
68
+ cd "$REPO_ROOT"
69
+
70
+ CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
71
+ [[ "$CURRENT_BRANCH" == "publish" ]] \
72
+ || hard_stop "Run from 'publish' (currently on '$CURRENT_BRANCH')."
73
+
74
+ [[ -z "$(git status --porcelain)" ]] \
75
+ || hard_stop "Uncommitted changes present. Commit or stash first."
76
+
77
+ if git rev-parse --verify "refs/tags/$VERSION" >/dev/null 2>&1; then
78
+ hard_stop "Tag '$VERSION' already exists locally. Pick a higher version."
79
+ fi
80
+
81
+ if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null 2>&1; then
82
+ hard_stop "Tag '$VERSION' already exists on origin. Pick a higher version."
83
+ fi
84
+
85
+ # ── Version-consistency + monotonic-release guard ─────────────────────────────
86
+ # The tag must match framework/VERSION and never be lower than the latest release.
87
+ FW_VER="$(cat "$REPO_ROOT/framework/VERSION" 2>/dev/null | tr -d '[:space:]')"
88
+ if [[ -n "$FW_VER" && "${VERSION#v}" != "$FW_VER" ]]; then
89
+ hard_stop "Release tag $VERSION != framework/VERSION ($FW_VER). Bump framework/VERSION or fix the tag."
90
+ fi
91
+ LAST_TAG="$(git tag -l 'v*.*.*' | sort -V | tail -n1)"
92
+ if [[ -n "$LAST_TAG" ]]; then
93
+ LO="$(printf '%s\n%s\n' "${VERSION#v}" "${LAST_TAG#v}" | sort -V | head -n1)"
94
+ if [[ "$LO" == "${VERSION#v}" && "${VERSION#v}" != "${LAST_TAG#v}" ]]; then
95
+ hard_stop "Release $VERSION is LOWER than the latest release $LAST_TAG — refusing a non-monotonic release."
96
+ fi
97
+ fi
98
+
99
+ # Verify git identity is set — tag creation needs it. Fail fast here rather
100
+ # than after a 3-minute smoke run.
101
+ GIT_NAME=$(git config user.name 2>/dev/null || echo "")
102
+ GIT_MAIL=$(git config user.email 2>/dev/null || echo "")
103
+ if [[ -z "$GIT_NAME" || -z "$GIT_MAIL" ]] \
104
+ && [[ -z "${GIT_COMMITTER_NAME:-}" || -z "${GIT_COMMITTER_EMAIL:-}" ]]; then
105
+ hard_stop "git user.name/user.email not configured (and GIT_COMMITTER_* not in env).
106
+ Set them before tagging:
107
+ git config user.name 'Your Name'
108
+ git config user.email 'you@example.com'
109
+ Or export GIT_COMMITTER_NAME + GIT_COMMITTER_EMAIL (and the matching GIT_AUTHOR_*)
110
+ in your shell, then re-run this script."
111
+ fi
112
+
113
+ info "Fetching latest from origin..."
114
+ git fetch origin publish --tags >/dev/null 2>&1 \
115
+ || hard_stop "git fetch failed"
116
+
117
+ LOCAL_SHA=$(git rev-parse HEAD)
118
+ REMOTE_SHA=$(git rev-parse origin/publish)
119
+ [[ "$LOCAL_SHA" == "$REMOTE_SHA" ]] \
120
+ || hard_stop "Local publish ($LOCAL_SHA) is not in sync with origin/publish ($REMOTE_SHA). Push or pull first."
121
+
122
+ ok "on publish, clean, in sync with origin"
123
+
124
+ # ── Phase 1: smoke gate ──────────────────────────────────────────────────────
125
+
126
+ header "Release gate: end-to-end smoke test"
127
+
128
+ if $SKIP_SMOKE; then
129
+ warn "WARNING: --skip-smoke specified. The smoke test gates exist to catch"
130
+ warn "regressions that the per-test suites do not. Skipping is for emergencies"
131
+ warn "(e.g., the smoke fixture itself is broken upstream). The last 4 releases"
132
+ warn "all needed hotfix-on-hotfix because we shipped without this gate."
133
+ echo ""
134
+ read -p " Type 'SKIP-SMOKE' verbatim to confirm bypass: " confirm
135
+ [[ "$confirm" == "SKIP-SMOKE" ]] || hard_stop "Bypass not confirmed. Aborting."
136
+ warn "Smoke skipped by maintainer confirmation."
137
+ else
138
+ info "Running tests/e2e_smoke.sh --always-clean..."
139
+ echo ""
140
+ bash "$REPO_ROOT/tests/e2e_smoke.sh" --always-clean \
141
+ || hard_stop "Smoke test failed. Fix the regression before releasing $VERSION."
142
+ ok "smoke passed"
143
+ fi
144
+
145
+ # ── Phase 2: tag publish ──────────────────────────────────────────────────────
146
+
147
+ header "Tagging $VERSION on publish at $LOCAL_SHA"
148
+
149
+ git tag -a "$VERSION" -m "Release $VERSION
150
+
151
+ Tagged via scripts/release-to-public.sh from publish branch.
152
+ Smoke test: $($SKIP_SMOKE && echo 'SKIPPED (maintainer override)' || echo 'passed')
153
+ " \
154
+ || hard_stop "git tag failed"
155
+ ok "tag $VERSION created locally"
156
+
157
+ # ── Phase 3: push tag to origin ──────────────────────────────────────────────
158
+
159
+ info "Pushing tag $VERSION to origin..."
160
+ if git push origin "$VERSION" >/dev/null 2>&1; then
161
+ ok "tag pushed to origin"
162
+ else
163
+ err "git push origin $VERSION failed."
164
+ err "The tag exists locally but isn't published. To recover:"
165
+ err " git tag -d $VERSION # remove local tag, then retry"
166
+ err " bash scripts/release-to-public.sh $VERSION"
167
+ exit 1
168
+ fi
169
+
170
+ # ── Done ──────────────────────────────────────────────────────────────────────
171
+
172
+ echo ""
173
+ echo -e "${BOLD}${GREEN}✓ Released $VERSION${NC}"
174
+ echo ""
175
+ echo "Next steps for the maintainer:"
176
+ echo " 1. Sync publish → main:"
177
+ echo " git checkout main"
178
+ echo " bash scripts/sync-from-publish.sh"
179
+ echo ""
180
+ echo " 2. Mirror publish to the public template repo:"
181
+ echo " (run your mirror script, or push publish + tag to the public remote)"
182
+ echo ""
183
+ echo " 3. (Optional) Create a GitHub Release on the public repo:"
184
+ echo " gh release create $VERSION --notes-from-tag --repo svayam-opensource/governed-agentic-dev-framework"
185
+ echo ""
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env bash
2
+ # Script: render-harness
3
+ # Purpose: Render per-tool agent entrypoints from the ONE canonical protocol so
4
+ # they never drift. Source of truth:
5
+ # - agent/session-protocol.md (the protocol body)
6
+ # - agent/harness-manifest.yaml (which tools, paths, templates, tiers)
7
+ # Regenerates every harness with generated:true and status:active.
8
+ # CLAUDE.md (import tier, generated:false) is hand-maintained and never
9
+ # overwritten — it @imports the canonical files instead.
10
+ # Usage:
11
+ # bash render-harness.sh # (re)render all generated root files
12
+ # bash render-harness.sh --check # verify on-disk files match; exit 1 on drift (CI gate)
13
+ # bash render-harness.sh --list # list every registered harness + tier + status
14
+ # bash render-harness.sh --project <PID> # write per-project entrypoints under projects/<PID>/
15
+ # Compliance: keeps the POL-113..117 protocol single-sourced (one edit, re-render).
16
+
17
+ set -euo pipefail
18
+
19
+ REPO="$(cd "$(dirname "$0")/.." && pwd)"
20
+ MANIFEST="$REPO/agent/harness-manifest.yaml"
21
+ PROTOCOL="$REPO/agent/session-protocol.md"
22
+ ENTRYPOINT="$REPO/framework/agent.md"
23
+
24
+ [[ -f "$MANIFEST" ]] || { echo "ERROR: manifest not found: $MANIFEST" >&2; exit 1; }
25
+ [[ -f "$PROTOCOL" ]] || { echo "ERROR: canonical protocol not found: $PROTOCOL" >&2; exit 1; }
26
+ command -v python3 >/dev/null 2>&1 || { echo "ERROR: python3 is required" >&2; exit 1; }
27
+
28
+ MODE="render"; PID=""
29
+ while [[ $# -gt 0 ]]; do
30
+ case "$1" in
31
+ --check) MODE="check" ;;
32
+ --list) MODE="list" ;;
33
+ --project) MODE="project"; PID="${2:-}"; shift; [[ -n "$PID" ]] || { echo "ERROR: --project needs a <PID>" >&2; exit 1; } ;;
34
+ -h|--help) sed -n '2,20p' "$0"; exit 0 ;;
35
+ *) echo "ERROR: unknown argument: $1" >&2; exit 1 ;;
36
+ esac
37
+ shift
38
+ done
39
+
40
+ python3 - "$MANIFEST" "$PROTOCOL" "$ENTRYPOINT" "$REPO" "$MODE" "$PID" <<'PY'
41
+ import sys, os, yaml
42
+
43
+ manifest_path, protocol_path, entrypoint_path, repo, mode, pid = sys.argv[1:7]
44
+
45
+ M = yaml.safe_load(open(manifest_path)) or {}
46
+ banner = (M.get("generated_banner") or "").strip()
47
+ templates = M.get("templates", {}) or {}
48
+ harnesses = M.get("harnesses", []) or []
49
+ body = open(protocol_path).read().rstrip("\n")
50
+
51
+ def subst(tmpl, mapping, extra=None):
52
+ out = tmpl
53
+ # Render markers are namespaced ({{render.X}} / {{template_extra.X}}) so the
54
+ # dot keeps them out of the org-placeholder regex ({{NAME}}, no dots), which
55
+ # setup.sh substitutes and STRICT_PLACEHOLDERS validates.
56
+ for k, v in mapping.items():
57
+ out = out.replace("{{render.%s}}" % k, v)
58
+ for k, v in (extra or {}).items():
59
+ sv = "true" if v is True else "false" if v is False else str(v)
60
+ out = out.replace("{{template_extra.%s}}" % k, sv)
61
+ return out.rstrip("\n") + "\n"
62
+
63
+ def render_file(harness, body_text):
64
+ """Render one harness's file content from its named template."""
65
+ tname = harness.get("template")
66
+ if not tname or tname not in templates:
67
+ raise SystemExit("ERROR: harness '%s' references unknown template '%s'" % (harness.get("id"), tname))
68
+ return subst(templates[tname],
69
+ {"generated_banner": banner, "body": body_text},
70
+ harness.get("template_extra"))
71
+
72
+ def generated_active():
73
+ return [h for h in harnesses if h.get("generated") and h.get("status") == "active"]
74
+
75
+ def write(path, content):
76
+ os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
77
+ open(path, "w").write(content)
78
+
79
+ # ── Mode: list ───────────────────────────────────────────────────────────────
80
+ if mode == "list":
81
+ print("%-18s %-22s %-15s %-9s %-7s %s" % ("id", "tool", "tier", "status", "gen", "path"))
82
+ print("-" * 100)
83
+ for h in harnesses:
84
+ print("%-18s %-22s %-15s %-9s %-7s %s" % (
85
+ h.get("id", "?"), (h.get("tool") or "")[:22], h.get("tier", "?"),
86
+ h.get("status", "?"), str(bool(h.get("generated"))).lower(), h.get("path") or "(none)"))
87
+ print("\nGenerated on `render`: " + ", ".join(h["id"] for h in generated_active()))
88
+ sys.exit(0)
89
+
90
+ # ── Mode: render / check (root install paths) ────────────────────────────────
91
+ if mode in ("render", "check"):
92
+ drift, wrote = [], []
93
+ for h in generated_active():
94
+ path = os.path.join(repo, h["path"])
95
+ content = render_file(h, body)
96
+ if mode == "check":
97
+ existing = open(path).read() if os.path.exists(path) else None
98
+ if existing != content:
99
+ drift.append(h["path"])
100
+ else:
101
+ write(path, content)
102
+ wrote.append(h["path"])
103
+ if mode == "check":
104
+ if drift:
105
+ print("DRIFT — these generated files are out of sync with agent/session-protocol.md:")
106
+ for d in drift:
107
+ print(" - " + d)
108
+ print("\nRun: ./scripts/render-harness.sh")
109
+ sys.exit(1)
110
+ print("OK — all %d generated harness files are in sync." % len(generated_active()))
111
+ sys.exit(0)
112
+ for w in wrote:
113
+ print("rendered: " + w)
114
+ print("\n%d files rendered from agent/session-protocol.md." % len(wrote))
115
+ print("Note: CLAUDE.md is import-tier (hand-maintained) — not regenerated.")
116
+ sys.exit(0)
117
+
118
+ # ── Mode: project (per-project entrypoints under projects/<PID>/) ────────────
119
+ if mode == "project":
120
+ proj_dir = os.path.join(repo, "projects", pid)
121
+ if not os.path.isdir(proj_dir):
122
+ raise SystemExit("ERROR: project dir not found: projects/%s (seed it first)" % pid)
123
+ # Compose a per-project body = canonical protocol + the project's own agent.md
124
+ # inlined (so non-import tools get full project context in one file).
125
+ pp_agent = os.path.join(proj_dir, "agent.md")
126
+ if os.path.exists(pp_agent):
127
+ proj_ctx = open(pp_agent).read().rstrip("\n")
128
+ else:
129
+ proj_ctx = "See `projects/%s/agent.md` for project-specific context." % pid
130
+ pp_body = body + "\n\n---\n\n# Project entrypoint — " + pid + "\n\n" + proj_ctx
131
+ wrote = []
132
+ for h in harnesses:
133
+ if h.get("status") != "active":
134
+ continue
135
+ # Claude: write the @import stub (expands canonical + project agent.md at launch).
136
+ if h.get("tier") == "import" and h.get("per_project_path"):
137
+ ppath = os.path.join(repo, h["per_project_path"].replace("{project_id}", pid))
138
+ stub = (h.get("per_project_template") or "").rstrip("\n") + "\n"
139
+ write(ppath, stub)
140
+ wrote.append(os.path.relpath(ppath, repo))
141
+ continue
142
+ # Generated tools: render their template into projects/<PID>/<path> with the composed body.
143
+ if h.get("generated") and h.get("path"):
144
+ ppath = os.path.join(proj_dir, h["path"])
145
+ write(ppath, render_file(h, pp_body))
146
+ wrote.append(os.path.relpath(ppath, repo))
147
+ for w in wrote:
148
+ print("rendered: " + w)
149
+ print("\n%d per-project entrypoints written under projects/%s/." % (len(wrote), pid))
150
+ sys.exit(0)
151
+ PY
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env bash
2
+ # Script: resume
3
+ # Purpose: Transitions a project from PAUSED to ACTIVE with mandatory base sync.
4
+ # Usage: bash resume.sh <project_id>
5
+ # Compliance: C01 for knowledge sync (POL-122); C02 for state transition (POL-049, POL-051)
6
+
7
+ set -euo pipefail
8
+ source "$(dirname "$0")/lib.sh"
9
+ load_config
10
+
11
+ # ── Inputs ────────────────────────────────────────────────────────────────────
12
+
13
+ PROJECT_ID="${1:-}"
14
+ [[ -n "$PROJECT_ID" ]] || hard_stop "Usage: $0 <project_id>"
15
+
16
+ echo "=== resume: $PROJECT_ID"
17
+ echo ""
18
+
19
+ PROJECT_YAML=$(get_project_yaml "$PROJECT_ID")
20
+ check_project_exists "$PROJECT_ID"
21
+
22
+ # ── Pre-conditions ────────────────────────────────────────────────────────────
23
+
24
+ require_project_status "$PROJECT_YAML" "paused"
25
+
26
+ ASSIGNED_TO=$(yaml_get "$PROJECT_YAML" "assigned_to") # display/audit cache
27
+ GH_PROJECT=$(yaml_get "$PROJECT_YAML" "github_project")
28
+ is_authorized_for_project "$GH_PROJECT" "$ASSIGNED_TO" \
29
+ || hard_stop "Not authorized to resume — you need write access to the project's GitHub Project ($GH_PROJECT)."
30
+
31
+ BRANCH=$(project_branch_for_id "$PROJECT_ID")
32
+
33
+ # ── C01: Mandatory DEFAULT_BRANCH sync for workspace repo ────────────────────
34
+
35
+ echo "[ C01 ] Syncing workspace repo: $DEFAULT_BRANCH → $BRANCH..."
36
+ cd "$REPO_ROOT"
37
+ git fetch origin "$DEFAULT_BRANCH"
38
+ git fetch origin "$BRANCH"
39
+ git checkout "$BRANCH"
40
+
41
+ # Merge DEFAULT_BRANCH into project branch — exit 2 on conflict (caller resolves)
42
+ if ! git merge --no-edit "origin/$DEFAULT_BRANCH" 2>/dev/null; then
43
+ echo ""
44
+ echo "MERGE CONFLICT: $DEFAULT_BRANCH → $BRANCH in workspace repo."
45
+ echo "Resolve conflicts manually, commit, then re-run: bash resume.sh $PROJECT_ID"
46
+ exit 2
47
+ fi
48
+ git push origin "$BRANCH"
49
+ info "Workspace repo synced."
50
+
51
+ # ── C01: Mandatory base_branch sync for each code repo ───────────────────────
52
+
53
+ while IFS= read -r repo_url; do
54
+ REPO_NAME=$(get_repo_name "$repo_url")
55
+ REPO_DIR="$(repo_clone_dir "$PROJECT_ID" "$REPO_NAME")"
56
+ REPO_BASE=$(get_repo_base_branch "$PROJECT_YAML" "$repo_url")
57
+
58
+ if [[ ! -e "$REPO_DIR/.git" ]]; then
59
+ warn "Repo $REPO_NAME not cloned locally — skipping sync."
60
+ continue
61
+ fi
62
+
63
+ echo "[ C01 ] Syncing $REPO_NAME: $REPO_BASE → $BRANCH..."
64
+ git -C "$REPO_DIR" fetch origin "$REPO_BASE"
65
+ git -C "$REPO_DIR" checkout "$BRANCH"
66
+ if ! git -C "$REPO_DIR" merge --no-edit "origin/$REPO_BASE" 2>/dev/null; then
67
+ echo ""
68
+ echo "MERGE CONFLICT: $REPO_BASE → $BRANCH in $REPO_NAME."
69
+ echo "Resolve conflicts manually, commit, then re-run: bash resume.sh $PROJECT_ID"
70
+ exit 2
71
+ fi
72
+ git -C "$REPO_DIR" push origin "$BRANCH"
73
+ info "$REPO_NAME synced."
74
+ done < <(get_project_repos "$PROJECT_YAML")
75
+
76
+ # ── Update project.yaml ───────────────────────────────────────────────────────
77
+
78
+ yaml_set "$PROJECT_YAML" "paused_at" "~"
79
+ yaml_set "$PROJECT_YAML" "status" "active"
80
+
81
+ TODAY=$(today)
82
+
83
+ cd "$REPO_ROOT"
84
+ git checkout "$BRANCH"
85
+ git add "projects/$PROJECT_ID/project.yaml"
86
+ git commit -m "resume: $PROJECT_ID (synced with $DEFAULT_BRANCH)"
87
+ git push origin "$BRANCH"
88
+
89
+ # ── Reflect resume in the registry index on the default branch + README mirror ─
90
+ registry_set_status_on_main "$PROJECT_ID" "active"
91
+ project_readme_mirror "$PROJECT_ID" "$(yaml_get "$PROJECT_YAML" github_project)" "active" \
92
+ "$ASSIGNED_TO" "$(yaml_get "$PROJECT_YAML" seeded_by)" "$BRANCH" || true
93
+
94
+ echo ""
95
+ echo "=== Project resumed."
96
+ echo " Status: active"
97
+ echo " All branches synced with their base branches."
98
+ echo ""
99
+ echo "[ C01 ] Reload all four knowledge layers fresh before starting work:"
100
+ echo " 1. $WORKSPACE_REPO/knowledge/"
101
+ echo " 2. $WORKSPACE_REPO/projects/$PROJECT_ID/knowledge/"
102
+ echo " 3. <repo>/knowledge/ for each repo"
103
+ echo " 4. \$PRJ_GOV_LOC/preferences/<your-gh-login>.md (your own only)"