@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.
- package/LICENSE +21 -0
- package/README.md +123 -0
- package/agent/harness-manifest.yaml +225 -0
- package/agent/session-protocol.md +116 -0
- package/bin/prj +21 -0
- package/package.json +41 -0
- package/prj +2381 -0
- package/scripts/add-repo.sh +126 -0
- package/scripts/cancel.sh +157 -0
- package/scripts/close-knowledge.sh +250 -0
- package/scripts/close-project.sh +233 -0
- package/scripts/create-task.sh +226 -0
- package/scripts/install-deps.sh +292 -0
- package/scripts/join.sh +89 -0
- package/scripts/lib.sh +841 -0
- package/scripts/merge-task.sh +163 -0
- package/scripts/onboard-repo.sh +275 -0
- package/scripts/pause.sh +80 -0
- package/scripts/project-access.sh +34 -0
- package/scripts/propose-knowledge.sh +168 -0
- package/scripts/release-to-public.sh +185 -0
- package/scripts/render-harness.sh +151 -0
- package/scripts/resume.sh +103 -0
- package/scripts/seed.sh +774 -0
- package/scripts/sync-from-publish.sh +193 -0
- package/scripts/sync.sh +90 -0
- package/scripts/test-merge.sh +100 -0
- package/scripts/validate/check_knowledge.py +158 -0
- package/scripts/validate/check_privacy.py +211 -0
- package/scripts/validate/check_protocol.py +117 -0
- package/scripts/validate/check_secrets.py +175 -0
- package/scripts/validate/run.py +391 -0
- package/setup.sh +529 -0
|
@@ -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)"
|