@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/setup.sh ADDED
@@ -0,0 +1,529 @@
1
+ #!/usr/bin/env bash
2
+ # Agentic Development Framework — Organization Setup Script
3
+ #
4
+ # One-time interactive setup for your organization. Writes org-specific values
5
+ # to org-config.yaml and configures git remotes so future framework upgrades
6
+ # can be pulled from the upstream TEMPLATE.
7
+ #
8
+ # Framework files (CLAUDE.md, AGENTS.md, knowledge/policies/, etc.) are NEVER
9
+ # modified by this script — they reference values from org-config.yaml at
10
+ # runtime. This keeps `git pull template main` conflict-free forever.
11
+ #
12
+ # Usage:
13
+ # bash setup.sh # interactive (default)
14
+ # bash setup.sh --non-interactive # re-use existing org-config.yaml values
15
+ # # (for CI / re-runs)
16
+ #
17
+ # Env escape hatches (testing):
18
+ # SETUP_SKIP_GITHUB_VERIFY=1 Skip the gh / scope checks.
19
+ # SETUP_SKIP_REMOTE_CONFIG=1 Skip rename/add of origin/template remotes.
20
+
21
+ set -euo pipefail
22
+
23
+ NON_INTERACTIVE=false
24
+ [[ "${1:-}" == "--non-interactive" ]] && NON_INTERACTIVE=true
25
+
26
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ CONFIG="$REPO_ROOT/org-config.yaml"
28
+
29
+ # The framework's canonical upstream — used to seed the `template` remote.
30
+ TEMPLATE_REPO_URL="git@github.com:svayam-opensource/governed-agentic-dev-framework.git"
31
+ TEMPLATE_OWNER="svayam-opensource"
32
+ TEMPLATE_REPO="governed-agentic-dev-framework"
33
+
34
+ # ── Output helpers ────────────────────────────────────────────────────────────
35
+
36
+ BOLD='\033[1m'; DIM='\033[2m'
37
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
38
+
39
+ ok() { echo -e "${GREEN} ✓${NC} $*"; }
40
+ warn() { echo -e "${YELLOW} !${NC} $*"; }
41
+ err() { echo -e "${RED} ✗${NC} $*" >&2; }
42
+ info() { echo -e "${CYAN} →${NC} $*"; }
43
+ header() { echo ""; echo -e "${BOLD}${CYAN}$*${NC}"; }
44
+ hard_stop() { echo ""; err "$*"; echo ""; exit 1; }
45
+
46
+ # ── Read with EOF handling ────────────────────────────────────────────────────
47
+
48
+ # Helpers below use unique internal variable names (__rabort_val, __ask_val,
49
+ # __validated_val) to avoid shadowing the caller's __val via bash's dynamic
50
+ # scoping. printf -v writes to the closest local in scope — if both inner
51
+ # and outer scopes declare `local __val`, the inner wins and the value
52
+ # never propagates back to the caller.
53
+
54
+ _read_or_abort() {
55
+ local __varname="$1" __rabort_val
56
+ if ! IFS= read -r __rabort_val; then
57
+ echo ""
58
+ err "Aborted (no input)."
59
+ exit 1
60
+ fi
61
+ printf -v "$__varname" '%s' "$__rabort_val"
62
+ }
63
+
64
+ ask() {
65
+ local __var="$1" __prompt="$2" __default="${3:-}" __ask_val
66
+ if [[ -n "$__default" ]]; then
67
+ printf " ${BOLD}%s${NC} ${DIM}[%s]${NC}: " "$__prompt" "$__default"
68
+ else
69
+ printf " ${BOLD}%s${NC}: " "$__prompt"
70
+ fi
71
+ _read_or_abort __ask_val
72
+ printf -v "$__var" '%s' "${__ask_val:-$__default}"
73
+ }
74
+
75
+ ask_required() {
76
+ local __var="$1" __prompt="$2" __default="${3:-}" __validated_val
77
+ while true; do
78
+ ask __validated_val "$__prompt" "$__default"
79
+ if [[ -n "$__validated_val" ]]; then
80
+ printf -v "$__var" '%s' "$__validated_val"
81
+ return
82
+ fi
83
+ err "Required."
84
+ done
85
+ }
86
+
87
+ ask_slug() {
88
+ local __var="$1" __prompt="$2" __default="${3:-}" __validated_val
89
+ while true; do
90
+ ask __validated_val "$__prompt" "$__default"
91
+ if [[ "$__validated_val" =~ ^[A-Z][A-Z0-9]{1,5}$ ]]; then
92
+ printf -v "$__var" '%s' "$__validated_val"
93
+ return
94
+ fi
95
+ err "Must be 2-6 uppercase letters/digits, starting with a letter (e.g. ACME, NORDIC, SVM2)."
96
+ done
97
+ }
98
+
99
+ ask_date() {
100
+ local __var="$1" __prompt="$2" __default="${3:-}" __validated_val
101
+ while true; do
102
+ ask __validated_val "$__prompt" "$__default"
103
+ if [[ "$__validated_val" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
104
+ printf -v "$__var" '%s' "$__validated_val"
105
+ return
106
+ fi
107
+ err "Must be YYYY-MM-DD format."
108
+ done
109
+ }
110
+
111
+ # Parse a GitHub URL into owner/repo. Accepts ssh and https forms (with or
112
+ # without .git suffix). Returns 0 on success and sets the two named vars.
113
+ parse_github_url() {
114
+ local url="$1" __owner_var="$2" __repo_var="$3"
115
+ local normalized="${url%.git}"
116
+ if [[ "$normalized" =~ github\.com[:/]([^/]+)/(.+)$ ]]; then
117
+ printf -v "$__owner_var" '%s' "${BASH_REMATCH[1]}"
118
+ printf -v "$__repo_var" '%s' "${BASH_REMATCH[2]}"
119
+ return 0
120
+ fi
121
+ return 1
122
+ }
123
+
124
+ ask_github_url() {
125
+ local __var="$1" __prompt="$2" __default="${3:-}" __validated_val __owner __repo
126
+ while true; do
127
+ ask __validated_val "$__prompt" "$__default"
128
+ if [[ -z "$__validated_val" ]]; then
129
+ err "Required."
130
+ continue
131
+ fi
132
+ if parse_github_url "$__validated_val" __owner __repo; then
133
+ printf -v "$__var" '%s' "$__validated_val"
134
+ return
135
+ fi
136
+ err "Expected a GitHub URL (git@github.com:owner/repo[.git] or https://github.com/owner/repo[.git])."
137
+ done
138
+ }
139
+
140
+ # ── Read existing org-config.yaml values ──────────────────────────────────────
141
+
142
+ read_yaml_field() {
143
+ local key="$1"
144
+ if command -v yq &>/dev/null; then
145
+ local v
146
+ v=$(yq ".$key" "$CONFIG" 2>/dev/null)
147
+ [[ "$v" == "null" ]] && echo "" || echo "$v"
148
+ else
149
+ python3 -c "import yaml; v = yaml.safe_load(open('$CONFIG')).get('$key', ''); print(v if v is not None else '')" 2>/dev/null
150
+ fi
151
+ }
152
+
153
+ # ── Step 1: Pre-conditions ────────────────────────────────────────────────────
154
+
155
+ [[ -f "$CONFIG" ]] || hard_stop "org-config.yaml not found at $CONFIG"
156
+
157
+ cd "$REPO_ROOT"
158
+
159
+ if ! git rev-parse --git-dir &>/dev/null; then
160
+ hard_stop "Not in a git repository. Initialize first: git init"
161
+ fi
162
+
163
+ ORIGIN_URL=$(git remote get-url origin 2>/dev/null || echo "")
164
+ if [[ -z "$ORIGIN_URL" ]]; then
165
+ hard_stop "No 'origin' remote configured. Set one up first:
166
+ git remote add origin <YOUR_ORG_REPO_URL>
167
+ Then re-run setup.sh."
168
+ fi
169
+
170
+ ORIGIN_OWNER=""
171
+ ORIGIN_REPO=""
172
+ parse_github_url "$ORIGIN_URL" ORIGIN_OWNER ORIGIN_REPO \
173
+ || hard_stop "Could not parse owner/repo from origin URL: $ORIGIN_URL"
174
+
175
+ # Detect "origin still points at the framework TEMPLATE" — adopter cloned
176
+ # TEMPLATE directly. We re-point origin at their org repo during setup
177
+ # (not a hard stop the way it used to be — setup itself does the fix).
178
+ ORIGIN_IS_TEMPLATE=false
179
+ if [[ "$ORIGIN_OWNER" == "$TEMPLATE_OWNER" && "$ORIGIN_REPO" == "$TEMPLATE_REPO" ]]; then
180
+ ORIGIN_IS_TEMPLATE=true
181
+ fi
182
+
183
+ # ── Step 2: Read existing config + runtime detections ────────────────────────
184
+
185
+ CURRENT_ORG_NAME=$(read_yaml_field org_name)
186
+ CURRENT_ORG_SHORT=$(read_yaml_field org_short_name)
187
+ CURRENT_ORG_SLUG=$(read_yaml_field org_slug)
188
+ CURRENT_ORG_REPO_URL=$(read_yaml_field org_repo_url)
189
+ CURRENT_DEFAULT_BRANCH=$(read_yaml_field default_branch)
190
+ CURRENT_DEFAULT_CODE_BRANCH=$(read_yaml_field default_code_branch)
191
+ CURRENT_AGENT_WORK_ROOT=$(read_yaml_field agent_work_root)
192
+ CURRENT_POLICY_OWNER_EMAIL=$(read_yaml_field policy_owner_email)
193
+ CURRENT_POLICY_OWNER_GITHUB=$(read_yaml_field policy_owner_github)
194
+ CURRENT_LEGAL=$(read_yaml_field legal_owner_github)
195
+ CURRENT_INFRA=$(read_yaml_field infra_owner_github)
196
+ CURRENT_SYS_ARCH=$(read_yaml_field system_arch_owner_github)
197
+ CURRENT_DATA_ARCH=$(read_yaml_field data_arch_owner_github)
198
+ CURRENT_POLICY_DATE=$(read_yaml_field policy_effective_date)
199
+
200
+ GH_USER=$(gh api user --jq .login 2>/dev/null || echo "")
201
+ GIT_EMAIL=$(git config user.email 2>/dev/null || echo "")
202
+ TODAY=$(date +%Y-%m-%d)
203
+
204
+ # ── Step 3: Prompt (or skip in --non-interactive) ─────────────────────────────
205
+
206
+ if $NON_INTERACTIVE; then
207
+ warn "--non-interactive: using existing org-config.yaml values as-is."
208
+ [[ -n "$CURRENT_ORG_NAME" ]] \
209
+ || hard_stop "org-config.yaml has no org_name. Run setup.sh interactively first."
210
+ ORG_NAME="$CURRENT_ORG_NAME"
211
+ ORG_SHORT_NAME="$CURRENT_ORG_SHORT"
212
+ ORG_SLUG="$CURRENT_ORG_SLUG"
213
+ ORG_SLUG_LOWER=$(echo "$ORG_SLUG" | tr '[:upper:]' '[:lower:]')
214
+ ORG_REPO_URL="${CURRENT_ORG_REPO_URL:-$ORIGIN_URL}"
215
+ parse_github_url "$ORG_REPO_URL" ORIGIN_OWNER ORIGIN_REPO || true
216
+ GITHUB_ORG="$ORIGIN_OWNER"
217
+ WORKSPACE_REPO="$ORIGIN_REPO"
218
+ DEFAULT_BRANCH="$CURRENT_DEFAULT_BRANCH"
219
+ DEFAULT_CODE_BRANCH="$CURRENT_DEFAULT_CODE_BRANCH"
220
+ AGENT_WORK_ROOT="$CURRENT_AGENT_WORK_ROOT"
221
+ # Store a PORTABLE tilde path, not the expanded $HOME — this value is
222
+ # committed to org-config.yaml and shared with the whole team. lib.sh
223
+ # expands the leading ~ per-developer at runtime.
224
+ [[ -n "$AGENT_WORK_ROOT" ]] || AGENT_WORK_ROOT="~/.${ORG_SLUG_LOWER}/projects"
225
+ POLICY_OWNER_EMAIL="$CURRENT_POLICY_OWNER_EMAIL"
226
+ POLICY_OWNER_GITHUB="$CURRENT_POLICY_OWNER_GITHUB"
227
+ LEGAL_OWNER_GITHUB="$CURRENT_LEGAL"
228
+ INFRA_OWNER_GITHUB="$CURRENT_INFRA"
229
+ SYSTEM_ARCH_OWNER_GITHUB="$CURRENT_SYS_ARCH"
230
+ DATA_ARCH_OWNER_GITHUB="$CURRENT_DATA_ARCH"
231
+ POLICY_EFFECTIVE_DATE="$CURRENT_POLICY_DATE"
232
+ else
233
+ echo ""
234
+ echo -e "${BOLD}Agentic Development Framework — Setup${NC}"
235
+ echo "─────────────────────────────────────────"
236
+ echo ""
237
+ echo " Current 'origin': ${ORIGIN_OWNER}/${ORIGIN_REPO}"
238
+ [[ -n "$GH_USER" ]] && echo " Detected gh user: ${GH_USER}"
239
+ [[ -n "$GIT_EMAIL" ]] && echo " Git user.email: ${GIT_EMAIL}"
240
+ echo ""
241
+ echo "Press Enter to accept the [default] for any prompt."
242
+
243
+ if $ORIGIN_IS_TEMPLATE; then
244
+ header "Your organization's repository"
245
+ echo " Your 'origin' remote points at the framework TEMPLATE:"
246
+ echo " $ORIGIN_URL"
247
+ echo ""
248
+ echo " Setup will re-point origin at your org's own repo and keep TEMPLATE"
249
+ echo " as a separate remote called 'template' for future framework"
250
+ echo " upgrades (git pull template main)."
251
+ echo ""
252
+ ask_github_url ORG_REPO_URL "Your org's repo URL (git@... or https://...)" "${CURRENT_ORG_REPO_URL:-}"
253
+ parse_github_url "$ORG_REPO_URL" ORIGIN_OWNER ORIGIN_REPO \
254
+ || hard_stop "Internal error: just-validated URL no longer parses."
255
+ else
256
+ ORG_REPO_URL="${CURRENT_ORG_REPO_URL:-$ORIGIN_URL}"
257
+ fi
258
+ GITHUB_ORG="$ORIGIN_OWNER"
259
+ WORKSPACE_REPO="$ORIGIN_REPO"
260
+
261
+ header "Organization"
262
+ ask_required ORG_NAME "Full legal name of your organization" "$CURRENT_ORG_NAME"
263
+ ask_required ORG_SHORT_NAME "Short display name (used in headings)" "$CURRENT_ORG_SHORT"
264
+ ask_slug ORG_SLUG "Org slug (uppercase, 2-6 chars; e.g. ACME)" "$CURRENT_ORG_SLUG"
265
+ ORG_SLUG_LOWER=$(echo "$ORG_SLUG" | tr '[:upper:]' '[:lower:]')
266
+ echo ""
267
+ ok "org_slug: $ORG_SLUG"
268
+ ok "org_slug_lower: $ORG_SLUG_LOWER (auto-derived)"
269
+ ok "github_org: $ORIGIN_OWNER (from org repo URL)"
270
+ ok "workspace_repo: $ORIGIN_REPO (from org repo URL)"
271
+
272
+ header "Branches"
273
+ ask DEFAULT_BRANCH "Default branch for this workspace repo" "${CURRENT_DEFAULT_BRANCH:-main}"
274
+ ask DEFAULT_CODE_BRANCH "Default base branch for code repositories" "${CURRENT_DEFAULT_CODE_BRANCH:-dev}"
275
+
276
+ header "Agent work root"
277
+ echo " Per-project workspaces are created under this path. Each project"
278
+ echo " gets its own folder containing a clone of this repo on the project"
279
+ echo " branch plus clones of each impacted code repo on the project branch."
280
+ echo ""
281
+ ask AGENT_WORK_ROOT "Agent work root path" "${CURRENT_AGENT_WORK_ROOT:-~/.${ORG_SLUG_LOWER}/projects}"
282
+
283
+ header "Policy Owner (initial holder of all policy roles)"
284
+ ask_required POLICY_OWNER_EMAIL "Policy Owner email" "${CURRENT_POLICY_OWNER_EMAIL:-$GIT_EMAIL}"
285
+ ask_required POLICY_OWNER_GITHUB "Policy Owner GitHub @-handle" "${CURRENT_POLICY_OWNER_GITHUB:-${GH_USER:+@$GH_USER}}"
286
+
287
+ header "Domain Owners"
288
+ echo " Per the default policy, all domain roles are held by the Policy Owner"
289
+ echo " at launch. Press Enter to accept '$POLICY_OWNER_GITHUB' for each."
290
+ ask LEGAL_OWNER_GITHUB "Legal Owner" "${CURRENT_LEGAL:-$POLICY_OWNER_GITHUB}"
291
+ ask INFRA_OWNER_GITHUB "Infrastructure Owner" "${CURRENT_INFRA:-$POLICY_OWNER_GITHUB}"
292
+ ask SYSTEM_ARCH_OWNER_GITHUB "System Architecture Owner" "${CURRENT_SYS_ARCH:-$POLICY_OWNER_GITHUB}"
293
+ ask DATA_ARCH_OWNER_GITHUB "Data Architecture Owner" "${CURRENT_DATA_ARCH:-$POLICY_OWNER_GITHUB}"
294
+
295
+ header "Policy"
296
+ ask_date POLICY_EFFECTIVE_DATE "Policy effective date (YYYY-MM-DD)" "${CURRENT_POLICY_DATE:-$TODAY}"
297
+
298
+ echo ""
299
+ echo "Configured values:"
300
+ echo " org_name: $ORG_NAME"
301
+ echo " org_short_name: $ORG_SHORT_NAME"
302
+ echo " org_slug: $ORG_SLUG"
303
+ echo " org_repo_url: $ORG_REPO_URL"
304
+ echo " github_org: $GITHUB_ORG"
305
+ echo " workspace_repo: $WORKSPACE_REPO"
306
+ echo " default_branch: $DEFAULT_BRANCH"
307
+ echo " default_code_branch: $DEFAULT_CODE_BRANCH"
308
+ echo " agent_work_root: $AGENT_WORK_ROOT"
309
+ echo " policy_owner_email: $POLICY_OWNER_EMAIL"
310
+ echo " policy_owner_github: $POLICY_OWNER_GITHUB"
311
+ echo " legal_owner_github: $LEGAL_OWNER_GITHUB"
312
+ echo " infra_owner_github: $INFRA_OWNER_GITHUB"
313
+ echo " system_arch_owner_github: $SYSTEM_ARCH_OWNER_GITHUB"
314
+ echo " data_arch_owner_github: $DATA_ARCH_OWNER_GITHUB"
315
+ echo " policy_effective_date: $POLICY_EFFECTIVE_DATE"
316
+ echo ""
317
+ fi
318
+
319
+ # ── Step 4: Write org-config.yaml ─────────────────────────────────────────────
320
+
321
+ cat > "$CONFIG" <<EOF
322
+ # Agentic Development Framework — Organization Configuration
323
+ #
324
+ # Single source of truth for this organization's identity, defaults, and roles.
325
+ # Framework scripts and agents read these values at runtime — no placeholder
326
+ # substitution happens, so this file is the only thing that diverges from
327
+ # the upstream framework template.
328
+ #
329
+ # Re-run ./setup.sh to update. Do not edit by hand unless you know what you're
330
+ # doing (setup.sh overwrites the file).
331
+
332
+ # Full legal name of your organization
333
+ org_name: "$ORG_NAME"
334
+
335
+ # Short display name (used in headings and prose)
336
+ org_short_name: "$ORG_SHORT_NAME"
337
+
338
+ # Uppercase slug for human display and multi-org disambiguation (2-6 chars).
339
+ # Not used in project IDs (which use a literal PRJ- prefix).
340
+ org_slug: "$ORG_SLUG"
341
+
342
+ # Lowercase derivation of org_slug — used for filesystem paths under
343
+ # agent_work_root. Auto-derived from org_slug.
344
+ org_slug_lower: "$ORG_SLUG_LOWER"
345
+
346
+ # Full URL of this workspace repository. 'origin' will be set to this.
347
+ org_repo_url: "$ORG_REPO_URL"
348
+
349
+ # GitHub organization or username (derived from org_repo_url)
350
+ github_org: "$GITHUB_ORG"
351
+
352
+ # Name of this workspace repository (derived from org_repo_url)
353
+ workspace_repo: "$WORKSPACE_REPO"
354
+
355
+ # Default branch name for this workspace repo
356
+ default_branch: "$DEFAULT_BRANCH"
357
+
358
+ # Default base branch for code repositories (used by seed script)
359
+ default_code_branch: "$DEFAULT_CODE_BRANCH"
360
+
361
+ # Per-project workspaces are created under this path. Each gets its own
362
+ # folder containing a clone of this workspace repo on the project branch
363
+ # plus clones of each impacted code repo on the project branch.
364
+ agent_work_root: "$AGENT_WORK_ROOT"
365
+
366
+ # Policy Owner details (initial holder of all policy roles at launch)
367
+ policy_owner_email: "$POLICY_OWNER_EMAIL"
368
+ policy_owner_github: "$POLICY_OWNER_GITHUB"
369
+
370
+ # Other role GitHub handles (update as roles are formally assigned)
371
+ legal_owner_github: "$LEGAL_OWNER_GITHUB"
372
+ infra_owner_github: "$INFRA_OWNER_GITHUB"
373
+ system_arch_owner_github: "$SYSTEM_ARCH_OWNER_GITHUB"
374
+ data_arch_owner_github: "$DATA_ARCH_OWNER_GITHUB"
375
+
376
+ # Effective date of the policy (YYYY-MM-DD)
377
+ policy_effective_date: "$POLICY_EFFECTIVE_DATE"
378
+ EOF
379
+
380
+ ok "Wrote $CONFIG"
381
+
382
+ # ── Step 5: Configure git remotes ─────────────────────────────────────────────
383
+
384
+ if $NON_INTERACTIVE || [[ "${SETUP_SKIP_REMOTE_CONFIG:-}" == "1" ]]; then
385
+ :
386
+ else
387
+ header "Configuring git remotes"
388
+
389
+ CURRENT_ORIGIN_URL=$(git remote get-url origin 2>/dev/null || echo "")
390
+ if [[ "$CURRENT_ORIGIN_URL" != "$ORG_REPO_URL" ]]; then
391
+ if $ORIGIN_IS_TEMPLATE; then
392
+ info "Renaming current 'origin' (TEMPLATE) → 'template'"
393
+ git remote get-url template &>/dev/null && git remote remove template
394
+ git remote rename origin template
395
+ info "Setting new 'origin' → $ORG_REPO_URL"
396
+ git remote add origin "$ORG_REPO_URL"
397
+ else
398
+ info "Updating 'origin' → $ORG_REPO_URL"
399
+ git remote set-url origin "$ORG_REPO_URL"
400
+ fi
401
+ else
402
+ ok "origin → $ORG_REPO_URL"
403
+ fi
404
+
405
+ if git remote get-url template &>/dev/null; then
406
+ CURRENT_TEMPLATE_URL=$(git remote get-url template)
407
+ if [[ "$CURRENT_TEMPLATE_URL" != "$TEMPLATE_REPO_URL" ]]; then
408
+ info "Updating 'template' → $TEMPLATE_REPO_URL"
409
+ git remote set-url template "$TEMPLATE_REPO_URL"
410
+ else
411
+ ok "template → $TEMPLATE_REPO_URL"
412
+ fi
413
+ else
414
+ info "Adding 'template' remote → $TEMPLATE_REPO_URL"
415
+ git remote add template "$TEMPLATE_REPO_URL"
416
+ fi
417
+
418
+ ok "Remotes:"
419
+ git remote -v | sed 's/^/ /'
420
+ fi
421
+
422
+ # ── Step 6: GitHub identity & access verification ────────────────────────────
423
+
424
+ if $NON_INTERACTIVE || [[ "${SETUP_SKIP_GITHUB_VERIFY:-}" == "1" ]]; then
425
+ :
426
+ else
427
+ header "Identity & GitHub access"
428
+
429
+ GIT_EMAIL=$(git config user.email 2>/dev/null || echo "")
430
+ if [[ -z "$GIT_EMAIL" ]]; then
431
+ warn "git config user.email is not set"
432
+ ask GIT_EMAIL "Your email for git commits" ""
433
+ if [[ -n "$GIT_EMAIL" ]]; then
434
+ git config --global user.email "$GIT_EMAIL" && ok "Set git user.email: $GIT_EMAIL"
435
+ else
436
+ hard_stop "git user.email is required: git config --global user.email 'you@example.com'"
437
+ fi
438
+ else
439
+ ok "git user.email: $GIT_EMAIL"
440
+ fi
441
+
442
+ GH_USER=$(gh api user --jq .login 2>/dev/null || echo "")
443
+ if [[ -z "$GH_USER" ]]; then
444
+ warn "Not logged in to the GitHub CLI (gh)."
445
+ if confirm_yn "Run 'gh auth login' now (recommended scopes will be requested)?"; then
446
+ gh auth login -h github.com -s "$GOV_SCOPES_CSV" || true
447
+ GH_USER=$(gh api user --jq .login 2>/dev/null || echo "")
448
+ fi
449
+ [[ -n "$GH_USER" ]] || hard_stop "Still not logged in. Run 'gh auth login' and re-run setup.sh."
450
+ fi
451
+ ok "gh user: $GH_USER"
452
+
453
+ if gh api "orgs/$GITHUB_ORG" &>/dev/null; then
454
+ ok "Read access to org '$GITHUB_ORG'"
455
+ elif gh api "users/$GITHUB_ORG" &>/dev/null; then
456
+ ok "'$GITHUB_ORG' is a user account (not an org) — accessible"
457
+ else
458
+ err "Cannot read '$GITHUB_ORG' — not found, or no access"
459
+ hard_stop "Verify github_org in org-config.yaml is correct, you are a member, and gh has 'read:org' scope:
460
+ gh auth refresh -h github.com -s read:org"
461
+ fi
462
+
463
+ SCOPES=$(gh auth status 2>&1 | grep -i "Token scopes" | head -1 | sed -E 's/.*Token scopes:[[:space:]]*//' | tr -d "'\"" || echo "")
464
+ if [[ -n "$SCOPES" ]]; then
465
+ ok "Token scopes: $SCOPES"
466
+ if ! echo "$SCOPES" | grep -qw "repo"; then
467
+ err "Missing required scope: repo"
468
+ hard_stop "Refresh: gh auth refresh -h github.com -s repo"
469
+ fi
470
+ if [[ "$GITHUB_ORG_TYPE" == "org" ]] && ! echo "$SCOPES" | grep -qw "read:org"; then
471
+ warn "Scope 'read:org' not detected — some org operations may fail"
472
+ echo " Refresh: gh auth refresh -h github.com -s read:org"
473
+ fi
474
+ else
475
+ warn "Could not determine token scopes — assuming sufficient"
476
+ fi
477
+
478
+ if git ls-remote origin HEAD &>/dev/null; then
479
+ ok "origin reachable"
480
+ else
481
+ warn "Could not contact 'origin' remote — verify with: git remote -v"
482
+ fi
483
+ if git ls-remote template HEAD &>/dev/null; then
484
+ ok "template reachable"
485
+ else
486
+ warn "Could not contact 'template' remote — verify with: git remote -v"
487
+ fi
488
+ fi
489
+
490
+ # ── Step 7: Bootstrap current user's preferences file ────────────────────────
491
+ #
492
+ # Per-user preferences live at $AGENT_WORK_ROOT/preferences/<gh-login>.md
493
+ # (POL-127). Copy the template here so the developer has a starting point.
494
+ # Never overwrite an existing file. Skip silently if no gh login is available.
495
+ #
496
+ # NOTE (#65/H6): this previously referenced an undefined $PRJ_GOV_LOC, which
497
+ # aborted setup.sh under `set -u`. AGENT_WORK_ROOT is resolved above (with a
498
+ # default); expand a leading ~ against $HOME so prefs land outside the repo.
499
+
500
+ PREFS_LOGIN=$(gh api user --jq .login 2>/dev/null || echo "")
501
+ PREFS_TEMPLATE="$REPO_ROOT/knowledge/guidance/preferences-template.md"
502
+ if [[ -n "$PREFS_LOGIN" ]] && [[ -f "$PREFS_TEMPLATE" ]]; then
503
+ PREFS_DIR="${AGENT_WORK_ROOT/#\~/$HOME}/preferences"
504
+ PREFS_FILE="$PREFS_DIR/$PREFS_LOGIN.md"
505
+ mkdir -p "$PREFS_DIR"
506
+ if [[ -f "$PREFS_FILE" ]]; then
507
+ ok "Preferences: $PREFS_FILE (kept existing)"
508
+ else
509
+ cp "$PREFS_TEMPLATE" "$PREFS_FILE"
510
+ ok "Preferences: $PREFS_FILE (created from template)"
511
+ fi
512
+ else
513
+ warn "Could not bootstrap preferences file (gh login unavailable)."
514
+ warn "It will be auto-created on first prj write op once gh auth is configured."
515
+ fi
516
+
517
+ # ── Done ──────────────────────────────────────────────────────────────────────
518
+
519
+ echo ""
520
+ echo -e "${BOLD}${GREEN}Configured framework for: $ORG_NAME ($ORG_SLUG)${NC}"
521
+ echo ""
522
+ echo "Next steps:"
523
+ echo " 1. Review changes: git diff org-config.yaml"
524
+ echo " 2. Commit + push: git add org-config.yaml && git commit -m 'configure framework for $ORG_NAME' && git push origin $DEFAULT_BRANCH"
525
+ echo " 3. Edit preferences: ${PREFS_FILE:-<agent_work_root>/preferences/<your-gh-login>.md}"
526
+ echo " 4. Start using: ./prj"
527
+ echo ""
528
+ echo " Framework upgrades: git fetch template && git merge template/$DEFAULT_BRANCH"
529
+ echo ""