aidevops 3.13.76 → 3.13.78

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,1314 @@
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # SPDX-FileCopyrightText: 2025-2026 Marcus Quinn
4
+ # =============================================================================
5
+ # aidevops Init and Scaffold Library
6
+ # =============================================================================
7
+ # Project initialisation and scaffolding functions extracted from aidevops.sh
8
+ # to keep the orchestrator under the 2000-line file-size threshold.
9
+ #
10
+ # Covers:
11
+ # 1. Scaffold helpers: _scaffold_contributing, _scaffold_security, _scaffold_coc,
12
+ # scaffold_repo_courtesy_files, _generate_security_section, scaffold_agents_md,
13
+ # _update_agents_md_security
14
+ # 2. Init helpers: _init_parse_features, _seed_mission_control_template,
15
+ # _init_scaffold_commands_symlinks, _init_scaffold_scope_gated_files
16
+ # 3. cmd_init: main init command dispatcher
17
+ #
18
+ # Usage: source "${SCRIPT_DIR}/aidevops-init-lib.sh"
19
+ #
20
+ # Dependencies:
21
+ # - INSTALL_DIR, AGENTS_DIR, CONFIG_DIR (set by aidevops.sh)
22
+ # - print_* helpers and utility functions (defined in aidevops.sh before sourcing)
23
+ # - register_repo(), get_repo_slug() from aidevops-repos-lib.sh (sourced first)
24
+ #
25
+ # Part of aidevops framework: https://aidevops.sh
26
+
27
+ # Apply strict mode only when executed directly (not when sourced)
28
+ [[ "${BASH_SOURCE[0]}" == "${0}" ]] && set -euo pipefail
29
+
30
+ # Include guard
31
+ [[ -n "${_AIDEVOPS_INIT_LIB_LOADED:-}" ]] && return 0
32
+ _AIDEVOPS_INIT_LIB_LOADED=1
33
+
34
+ # Scaffold standard repo courtesy files if they don't exist
35
+ # Scaffold helpers (extracted for complexity reduction)
36
+ _scaffold_contributing() {
37
+ local project_root="$1" repo_name="$2"
38
+ [[ -f "$project_root/CONTRIBUTING.md" ]] && return 1
39
+ local c="# Contributing to $repo_name"
40
+ c="$c"$'\n\n'"Thanks for your interest in contributing!"
41
+ c="$c"$'\n\n'"## Quick Start"$'\n\n'"1. Fork the repository"
42
+ c="$c"$'\n'"2. Create a branch: \`git checkout -b feature/your-feature\`"
43
+ c="$c"$'\n'"3. Make your changes"
44
+ c="$c"$'\n'"4. Commit with conventional commits: \`git commit -m \"feat: add new feature\"\`"
45
+ c="$c"$'\n'"5. Push and open a PR"
46
+ c="$c"$'\n\n'"## Commit Messages"$'\n\n'"We use [Conventional Commits](https://www.conventionalcommits.org/):"
47
+ c="$c"$'\n\n'"- \`feat:\` - New feature"$'\n'"- \`fix:\` - Bug fix"$'\n'"- \`docs:\` - Documentation only"
48
+ c="$c"$'\n'"- \`refactor:\` - Code change that neither fixes a bug nor adds a feature"$'\n'"- \`chore:\` - Maintenance tasks"
49
+ printf '%s\n' "$c" >"$project_root/CONTRIBUTING.md"
50
+ return 0
51
+ }
52
+
53
+ _scaffold_security() {
54
+ local project_root="$1"
55
+ [[ -f "$project_root/SECURITY.md" ]] && return 1
56
+ local se="" ge
57
+ ge=$(git -C "$project_root" config user.email 2>/dev/null || echo "")
58
+ [[ -n "$ge" ]] && se="$ge"
59
+ cat >"$project_root/SECURITY.md" <<SECEOF
60
+ # Security Policy
61
+
62
+ ## Reporting a Vulnerability
63
+
64
+ If you discover a security vulnerability, please report it privately.
65
+ SECEOF
66
+ [[ -n "$se" ]] && cat >>"$project_root/SECURITY.md" <<SECEOF
67
+
68
+ **Email:** $se
69
+
70
+ Please do not open public issues for security vulnerabilities.
71
+ SECEOF
72
+ return 0
73
+ }
74
+
75
+ _scaffold_coc() {
76
+ local project_root="$1"
77
+ [[ -f "$project_root/CODE_OF_CONDUCT.md" ]] && return 1
78
+ cat >"$project_root/CODE_OF_CONDUCT.md" <<'COCEOF'
79
+ # Contributor Covenant Code of Conduct
80
+
81
+ ## Our Pledge
82
+
83
+ We as members, contributors, and leaders pledge to make participation in our
84
+ community a harassment-free experience for everyone.
85
+
86
+ ## Our Standards
87
+
88
+ Examples of behavior that contributes to a positive environment:
89
+
90
+ - Using welcoming and inclusive language
91
+ - Being respectful of differing viewpoints and experiences
92
+ - Gracefully accepting constructive criticism
93
+ - Focusing on what is best for the community
94
+
95
+ ## Attribution
96
+
97
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
98
+ version 2.1.
99
+ COCEOF
100
+ return 0
101
+ }
102
+
103
+ # Scaffold standard repo courtesy files if they don't exist
104
+ # Creates: README.md, LICENCE, CHANGELOG.md, CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md
105
+ scaffold_repo_courtesy_files() {
106
+ local project_root="$1"
107
+ local scope="${2:-standard}" # Default to standard for backward compatibility
108
+ local created=0
109
+ local repo_name
110
+ repo_name=$(basename "$project_root")
111
+ local author_name
112
+ author_name=$(git -C "$project_root" config user.name 2>/dev/null || echo "")
113
+ local current_year
114
+ current_year=$(date +%Y)
115
+ print_info "Checking repo courtesy files (scope: $scope)..."
116
+
117
+ # README.md: requires "standard" scope
118
+ if _scope_includes "$scope" "standard"; then
119
+ if [[ ! -f "$project_root/README.md" ]]; then
120
+ local rc="# $repo_name"
121
+ if [[ -f "$project_root/.aidevops.json" ]]; then
122
+ local desc
123
+ desc=$(jq -r '.description // empty' "$project_root/.aidevops.json" 2>/dev/null || echo "")
124
+ [[ -n "$desc" ]] && rc="$rc"$'\n\n'"$desc"
125
+ fi
126
+ { [[ -f "$project_root/LICENCE" ]] || [[ -f "$project_root/LICENSE" ]]; } && rc="$rc"$'\n\n'"## Licence"$'\n\n'"See [LICENCE](LICENCE) for details."
127
+ printf '%s\n' "$rc" >"$project_root/README.md"
128
+ ((++created))
129
+ fi
130
+ fi
131
+
132
+ # LICENCE: requires "public" scope
133
+ if _scope_includes "$scope" "public"; then
134
+ if [[ ! -f "$project_root/LICENCE" ]] && [[ ! -f "$project_root/LICENSE" ]]; then
135
+ local lh="${author_name:-$(whoami)}"
136
+ cat >"$project_root/LICENCE" <<LICEOF
137
+ MIT License
138
+
139
+ Copyright (c) $current_year $lh
140
+
141
+ Permission is hereby granted, free of charge, to any person obtaining a copy
142
+ of this software and associated documentation files (the "Software"), to deal
143
+ in the Software without restriction, including without limitation the rights
144
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
145
+ copies of the Software, and to permit persons to whom the Software is
146
+ furnished to do so, subject to the following conditions:
147
+
148
+ The above copyright notice and this permission notice shall be included in all
149
+ copies or substantial portions of the Software.
150
+
151
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
152
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
153
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
154
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
155
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
156
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
157
+ SOFTWARE.
158
+ LICEOF
159
+ ((++created))
160
+ fi
161
+ fi
162
+
163
+ # CHANGELOG.md: requires "public" scope
164
+ if _scope_includes "$scope" "public"; then
165
+ if [[ ! -f "$project_root/CHANGELOG.md" ]]; then
166
+ cat >"$project_root/CHANGELOG.md" <<'CHEOF'
167
+ # Changelog
168
+
169
+ All notable changes to this project will be documented in this file.
170
+
171
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
172
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
173
+
174
+ ## [Unreleased]
175
+ CHEOF
176
+ ((++created))
177
+ fi
178
+ fi
179
+
180
+ # CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md: require "public" scope
181
+ if _scope_includes "$scope" "public"; then
182
+ _scaffold_contributing "$project_root" "$repo_name" && ((++created))
183
+ _scaffold_security "$project_root" && ((++created))
184
+ _scaffold_coc "$project_root" && ((++created))
185
+ fi
186
+
187
+ [[ $created -gt 0 ]] && print_success "Created $created repo courtesy file(s) (README, LICENCE, CHANGELOG, etc.)" || print_info "Repo courtesy files already exist"
188
+ return 0
189
+ }
190
+
191
+ # Generate the Security section content based on project type (t1412.11)
192
+ # Returns the content via stdout so callers can use it for create or update.
193
+ _generate_security_section() {
194
+ local project_root="$1"
195
+
196
+ # Detect project type for security recommendations
197
+ local is_js_ts=false
198
+ if [[ -f "$project_root/package.json" ]]; then
199
+ is_js_ts=true
200
+ fi
201
+
202
+ cat <<'SECHEADER'
203
+ ## Security
204
+
205
+ SECHEADER
206
+
207
+ if [[ "$is_js_ts" == "true" ]]; then
208
+ cat <<'SECEOF'
209
+ ### Prompt Injection Defense
210
+
211
+ Any feature that processes untrusted content (tool outputs, user input, webhook
212
+ payloads) and passes it to an LLM must defend against prompt injection. This is
213
+ a JS/TS project — use `@stackone/defender` to sanitize untrusted tool results
214
+ before they enter the LLM context:
215
+
216
+ ```bash
217
+ npm install @stackone/defender
218
+ ```
219
+
220
+ ```typescript
221
+ import { createPromptDefense } from '@stackone/defender';
222
+
223
+ const defense = createPromptDefense({ enableTier2: true, blockHighRisk: true });
224
+
225
+ // Sanitize untrusted tool output before adding to LLM context
226
+ const result = await defense.defendToolResult(toolResponse, 'tool_name');
227
+ if (!result.allowed) {
228
+ throw new Error('Blocked untrusted content');
229
+ }
230
+ const safeContent = result.sanitized;
231
+ ```
232
+
233
+ For features that don't use LLMs but process untrusted text (webhooks, form
234
+ submissions, API endpoints), validate and sanitize inputs at the boundary.
235
+
236
+ ### General Security Rules
237
+
238
+ - Never log or expose API keys, tokens, or credentials in output
239
+ - Store secrets via `aidevops secret set <NAME>` (gopass-encrypted) or
240
+ environment variables — never hardcode them in source
241
+ - Use `<PLACEHOLDER>` values in code examples; note the secure storage location
242
+ - Validate all external input (user input, webhook payloads, API responses)
243
+ - Pin third-party GitHub Actions to SHA hashes, not branch tags
244
+ - Run `aidevops security audit` periodically to check security posture
245
+ - See `~/.aidevops/agents/tools/security/prompt-injection-defender.md` for
246
+ the framework's prompt injection defense patterns
247
+ SECEOF
248
+ else
249
+ cat <<'SECEOF'
250
+ ### Prompt Injection Defense
251
+
252
+ Any feature that passes untrusted content to an LLM — user input, tool outputs,
253
+ retrieved documents, emails, tickets, or webhook payloads — must defend against
254
+ prompt injection. Sanitize and validate that content before including it in
255
+ prompts:
256
+
257
+ - Strip or escape control characters and instruction-like patterns
258
+ - Use structured prompt templates with clear system/user boundaries
259
+ - Never concatenate raw external content directly into system prompts
260
+ - Validate all externally sourced content (tool results, API responses, database
261
+ records) before inclusion in prompts
262
+ - Consider allowlist-based input validation where possible
263
+
264
+ ### General Security Rules
265
+
266
+ - Never log or expose API keys, tokens, or credentials in output
267
+ - Store secrets via `aidevops secret set <NAME>` (gopass-encrypted) or
268
+ environment variables — never hardcode them in source
269
+ - Use `<PLACEHOLDER>` values in code examples; note the secure storage location
270
+ - Validate all external input (user input, webhook payloads, API responses)
271
+ - Pin third-party GitHub Actions to SHA hashes, not branch tags
272
+ - Run `aidevops security audit` periodically to check security posture
273
+ - See `~/.aidevops/agents/tools/security/prompt-injection-defender.md` for
274
+ the framework's prompt injection defense patterns
275
+ SECEOF
276
+ fi
277
+
278
+ return 0
279
+ }
280
+
281
+ # Scaffold .agents/AGENTS.md with context-aware Security section (t1412.11)
282
+ # Idempotent: creates the file if missing, or updates the Security section
283
+ # in an existing file (preserving all other custom content).
284
+ scaffold_agents_md() {
285
+ local project_root="$1"
286
+ local agents_md="$project_root/.agents/AGENTS.md"
287
+
288
+ mkdir -p "$(dirname "$agents_md")"
289
+
290
+ if [[ -f "$agents_md" ]]; then
291
+ # File exists — update the Security section idempotently
292
+ _update_agents_md_security "$project_root"
293
+ return $?
294
+ fi
295
+
296
+ # File missing — create from scratch with base template + security
297
+ local security_content
298
+ security_content=$(_generate_security_section "$project_root")
299
+
300
+ cat >"$agents_md" <<'AGENTSEOF'
301
+ # Agent Instructions
302
+
303
+ This directory contains project-specific agent context. The [aidevops](https://aidevops.sh)
304
+ framework is loaded separately via the global config (`~/.aidevops/agents/`).
305
+
306
+ ## Purpose
307
+
308
+ Files in `.agents/` provide project-specific instructions that AI assistants
309
+ read when working in this repository. Use this for:
310
+
311
+ - Domain-specific conventions not covered by the framework
312
+ - Project architecture decisions and patterns
313
+ - API design rules, data models, naming conventions
314
+ - Integration details (third-party services, deployment targets)
315
+
316
+ ## Adding Agents
317
+
318
+ Create `.md` files in this directory for domain-specific context:
319
+
320
+ ```text
321
+ .agents/
322
+ AGENTS.md # This file - overview and index
323
+ api-patterns.md # API design conventions
324
+ deployment.md # Deployment procedures
325
+ data-model.md # Database schema and relationships
326
+ ```
327
+
328
+ Each file is read on demand by AI assistants when relevant to the task.
329
+
330
+ AGENTSEOF
331
+
332
+ # Append the generated security section
333
+ printf '%s\n' "$security_content" >>"$agents_md"
334
+
335
+ return 0
336
+ }
337
+
338
+ # Update the Security section in an existing .agents/AGENTS.md (t1412.11)
339
+ # Replaces everything from "## Security" to the next "## " heading (or EOF)
340
+ # with the latest security guidance. Preserves all other content.
341
+ _update_agents_md_security() {
342
+ local project_root="$1"
343
+ local agents_md="$project_root/.agents/AGENTS.md"
344
+ local tmp_file="${agents_md}.tmp.$$"
345
+
346
+ local security_content
347
+ security_content=$(_generate_security_section "$project_root")
348
+
349
+ local in_security=false
350
+ local has_security_section=false
351
+
352
+ # Process line by line: skip old Security section, insert new one
353
+ while IFS= read -r line || [[ -n "$line" ]]; do
354
+ # Match "## Security" exactly, with optional trailing whitespace
355
+ if [[ "$line" =~ ^'## Security'[[:space:]]*$ ]]; then
356
+ # Found the Security heading — replace it
357
+ in_security=true
358
+ has_security_section=true
359
+ printf '%s\n' "$security_content" >>"$tmp_file"
360
+ continue
361
+ fi
362
+
363
+ if [[ "$in_security" == "true" ]]; then
364
+ # Check if we've hit the next ## heading (end of Security section)
365
+ if [[ "$line" == "## "* ]]; then
366
+ in_security=false
367
+ printf '%s\n' "$line" >>"$tmp_file"
368
+ fi
369
+ # Skip lines within the old Security section
370
+ continue
371
+ fi
372
+
373
+ printf '%s\n' "$line" >>"$tmp_file"
374
+ done <"$agents_md"
375
+
376
+ if [[ "$has_security_section" == "false" ]]; then
377
+ # No existing Security section — append it
378
+ printf '\n%s\n' "$security_content" >>"$tmp_file"
379
+ fi
380
+
381
+ mv "$tmp_file" "$agents_md"
382
+
383
+ return 0
384
+ }
385
+
386
+ # Init helpers (extracted for complexity reduction)
387
+ _init_parse_features() {
388
+ local features="$1"
389
+ case "$features" in
390
+ all) echo "planning git_workflow code_quality time_tracking database beads security" ;;
391
+ planning) echo "planning" ;; git-workflow) echo "git_workflow" ;; code-quality) echo "code_quality" ;;
392
+ time-tracking) echo "time_tracking planning" ;; database) echo "database" ;;
393
+ beads) echo "beads planning" ;; sops) echo "sops" ;; security) echo "security" ;;
394
+ *)
395
+ local result=""
396
+ IFS=',' read -ra FL <<<"$features"
397
+ for f in "${FL[@]}"; do
398
+ case "$f" in
399
+ planning) result="$result planning" ;; git-workflow) result="$result git_workflow" ;;
400
+ code-quality) result="$result code_quality" ;; time-tracking) result="$result time_tracking planning" ;;
401
+ database) result="$result database" ;; beads) result="$result beads planning" ;;
402
+ sops) result="$result sops" ;; security) result="$result security" ;;
403
+ esac
404
+ done
405
+ echo "$result"
406
+ ;;
407
+ esac
408
+ return 0
409
+ }
410
+
411
+ # Seed mission-control onboarding template when initializing a mission-control repo.
412
+ # Usage: _seed_mission_control_template <project_root> <personal|org>
413
+ _seed_mission_control_template() {
414
+ local project_root="$1"
415
+ local scope="$2"
416
+ local seed_file="$project_root/todo/mission-control-seed.md"
417
+
418
+ if [[ -z "$scope" ]]; then
419
+ return 0
420
+ fi
421
+
422
+ if [[ -f "$seed_file" ]]; then
423
+ return 0
424
+ fi
425
+
426
+ mkdir -p "$project_root/todo"
427
+
428
+ if [[ "$scope" == "personal" ]]; then
429
+ cat >"$seed_file" <<'EOF'
430
+ # Mission Control Seed (Personal)
431
+
432
+ Starter checklist for a personal mission-control repo initialized with aidevops.
433
+
434
+ ## First-Day Setup
435
+
436
+ - [ ] Confirm `~/.config/aidevops/repos.json` has all active repos registered with correct `slug` and `path`
437
+ - [ ] Set `pulse: true` only for repos you want actively supervised
438
+ - [ ] Add `pulse_hours` windows to avoid dispatch during daytime manual development
439
+ - [ ] Verify profile and archive repos are `pulse: false` and `priority: "profile"` where applicable
440
+
441
+ ## Operating Rhythm
442
+
443
+ - [ ] Define weekly review cadence for `aidevops pulse` health and backlog aging
444
+ - [ ] Add hygiene tasks for stale branches, stale worktrees, and stale queued issues
445
+ - [ ] Track cross-repo blockers in TODO with clear `blocked-by:` links
446
+ EOF
447
+ else
448
+ cat >"$seed_file" <<'EOF'
449
+ # Mission Control Seed (Organization)
450
+
451
+ Starter checklist for an organization mission-control repo initialized with aidevops.
452
+
453
+ ## First-Day Setup
454
+
455
+ - [ ] Register all managed org repos in `~/.config/aidevops/repos.json` with `slug`, `path`, `priority`, and `maintainer`
456
+ - [ ] Set `pulse: true` only for repos approved for autonomous dispatch
457
+ - [ ] Configure `pulse_hours` and optional `pulse_expires` windows for sprint-based focus
458
+ - [ ] Keep sensitive/internal-only repos `pulse: false` until policy checks are complete
459
+
460
+ ## Governance
461
+
462
+ - [ ] Define maintainer response SLA for `needs-maintainer-review` triage
463
+ - [ ] Document worker guardrails (release, merge, and security boundaries)
464
+ - [ ] Add a weekly audit task for repo registration drift and label hygiene
465
+ EOF
466
+ fi
467
+
468
+ print_success "Seeded mission-control template: todo/mission-control-seed.md (${scope})"
469
+ return 0
470
+ }
471
+
472
+ # Scaffold .agents/commands/ and .windsurf/workflows/ symlinks so that clients
473
+ # which read repo-local command directories (Amp reads .agents/commands/ natively;
474
+ # Windsurf reads .windsurf/workflows/) see the aidevops main-agent slash commands.
475
+ #
476
+ # Behavior is idempotent:
477
+ # - If .agents/commands/ already contains the expected aidevops-*.md symlinks
478
+ # (this repo IS the aidevops source), do nothing.
479
+ # - Otherwise link .agents/commands/ → ~/.aidevops/agents/commands/
480
+ # - Always link .windsurf/workflows/ → ../.agents/commands/ (relative)
481
+ _init_scaffold_commands_symlinks() {
482
+ local project_root="$1"
483
+ local source_dir="$HOME/.aidevops/agents/commands"
484
+ local commands_dir="$project_root/.agents/commands"
485
+ local windsurf_dir="$project_root/.windsurf"
486
+ local workflows_link="$windsurf_dir/workflows"
487
+
488
+ # If .agents/commands/ already contains main-agent symlinks, this repo
489
+ # manages them directly (e.g. the aidevops source repo itself) — leave
490
+ # it alone so we never overwrite authoritative content.
491
+ if [[ -e "$commands_dir/aidevops-build-plus.md" ]]; then
492
+ print_info ".agents/commands/ already contains main-agent symlinks — preserving"
493
+ elif [[ ! -d "$source_dir" ]]; then
494
+ print_warning "Framework commands dir not found at $source_dir — run setup.sh first to deploy main-agent symlinks"
495
+ elif [[ -L "$commands_dir" ]]; then
496
+ # Existing symlink — point at the canonical source
497
+ local current_target
498
+ current_target=$(readlink "$commands_dir")
499
+ if [[ "$current_target" != "$source_dir" ]]; then
500
+ rm "$commands_dir"
501
+ ln -s "$source_dir" "$commands_dir"
502
+ print_success "Re-linked .agents/commands/ → $source_dir"
503
+ else
504
+ print_info ".agents/commands/ already linked correctly"
505
+ fi
506
+ elif [[ -d "$commands_dir" ]]; then
507
+ print_warning ".agents/commands/ exists as a real directory — not overwriting"
508
+ else
509
+ ln -s "$source_dir" "$commands_dir"
510
+ print_success "Linked .agents/commands/ → $source_dir (Amp reads this natively)"
511
+ fi
512
+
513
+ # .windsurf/workflows/ → ../.agents/commands/ (relative, so the link
514
+ # resolves inside the repo regardless of checkout path).
515
+ mkdir -p "$windsurf_dir"
516
+ if [[ -L "$workflows_link" ]]; then
517
+ print_info ".windsurf/workflows/ already linked"
518
+ elif [[ -d "$workflows_link" ]]; then
519
+ print_warning ".windsurf/workflows/ exists as a real directory — not overwriting"
520
+ else
521
+ (cd "$windsurf_dir" && ln -s "../.agents/commands" workflows)
522
+ print_success "Linked .windsurf/workflows/ → ../.agents/commands (Windsurf slash commands)"
523
+ fi
524
+ return 0
525
+ }
526
+
527
+ # Scaffold optional files gated by init_scope (t2265).
528
+ # Extracted from cmd_init to reduce nesting depth and function length.
529
+ # Usage: _init_scaffold_scope_gated_files <project_root> <init_scope> <repo_name>
530
+ _init_scaffold_scope_gated_files() {
531
+ local project_root="$1"
532
+ local init_scope="$2"
533
+ local repo_name="$3"
534
+
535
+ # Collaborator pointer files — require standard scope
536
+ if _scope_includes "$init_scope" "standard"; then
537
+ local pointer_content="Read AGENTS.md for all project context and instructions."
538
+ local pointer_files=(".cursorrules" ".windsurfrules" ".clinerules" ".github/copilot-instructions.md")
539
+ local pointer_created=0
540
+ local pf
541
+ for pf in "${pointer_files[@]}"; do
542
+ local pf_path="$project_root/$pf"
543
+ if [[ ! -f "$pf_path" ]]; then
544
+ mkdir -p "$(dirname "$pf_path")"
545
+ echo "$pointer_content" >"$pf_path"
546
+ ((++pointer_created))
547
+ fi
548
+ done
549
+ if [[ $pointer_created -gt 0 ]]; then
550
+ print_success "Created $pointer_created collaborator pointer file(s) (.cursorrules, etc.)"
551
+ else
552
+ print_info "Collaborator pointer files already exist"
553
+ fi
554
+ else
555
+ print_info "Collaborator pointer files skipped (init_scope: $init_scope)"
556
+ fi
557
+
558
+ # DESIGN.md — requires standard scope
559
+ if _scope_includes "$init_scope" "standard"; then
560
+ if [[ ! -f "$project_root/DESIGN.md" ]]; then
561
+ local design_template="$AGENTS_DIR/templates/DESIGN.md.template"
562
+ if [[ -f "$design_template" ]]; then
563
+ sed "s/{Project Name}/$repo_name/g" "$design_template" >"$project_root/DESIGN.md"
564
+ print_success "Created DESIGN.md (design system skeleton — populate with tools/design/design-md.md)"
565
+ fi
566
+ else
567
+ print_info "DESIGN.md already exists, skipping"
568
+ fi
569
+ else
570
+ print_info "DESIGN.md skipped (init_scope: $init_scope)"
571
+ fi
572
+
573
+ # Courtesy files (README, LICENCE, CHANGELOG, etc.) — scope handled internally
574
+ scaffold_repo_courtesy_files "$project_root" "$init_scope"
575
+
576
+ # MODELS.md — requires standard scope
577
+ if _scope_includes "$init_scope" "standard"; then
578
+ local generate_models_script="$AGENTS_DIR/scripts/generate-models-md.sh"
579
+ if [[ -x "$generate_models_script" ]] && command -v sqlite3 &>/dev/null; then
580
+ print_info "Generating MODELS.md (model performance leaderboard)..."
581
+ if "$generate_models_script" --output "$project_root/MODELS.md" --repo-path "$project_root" --quiet 2>/dev/null; then
582
+ print_success "Created MODELS.md (per-repo model leaderboard)"
583
+ else
584
+ print_warning "MODELS.md generation failed (will be populated as tasks run)"
585
+ fi
586
+ else
587
+ print_info "MODELS.md skipped (sqlite3 or generate script not available)"
588
+ fi
589
+ else
590
+ print_info "MODELS.md skipped (init_scope: $init_scope)"
591
+ fi
592
+
593
+ return 0
594
+ }
595
+
596
+ # Init command - initialize aidevops in a project
597
+ cmd_init() {
598
+ local features="${1:-all}"
599
+
600
+ print_header "Initialize AI DevOps in Project"
601
+ echo ""
602
+
603
+ # Check if we're in a git repo
604
+ if ! git rev-parse --is-inside-work-tree &>/dev/null; then
605
+ print_error "Not in a git repository"
606
+ print_info "Run 'git init' first or navigate to a git repository"
607
+ return 1
608
+ fi
609
+
610
+ # Check for protected branch and offer worktree
611
+ if ! check_protected_branch "chore" "aidevops-init"; then
612
+ return 1
613
+ fi
614
+
615
+ local project_root
616
+ project_root=$(git rev-parse --show-toplevel)
617
+ print_info "Project root: $project_root"
618
+ echo ""
619
+
620
+ # Parse features using helper
621
+ local parsed
622
+ parsed=$(_init_parse_features "$features")
623
+ local enable_planning=false enable_git_workflow=false enable_code_quality=false
624
+ local enable_time_tracking=false enable_database=false enable_beads=false
625
+ local enable_sops=false enable_security=false
626
+ local _f
627
+ for _f in $parsed; do
628
+ case "$_f" in
629
+ planning) enable_planning=true ;; git_workflow) enable_git_workflow=true ;;
630
+ code_quality) enable_code_quality=true ;; time_tracking) enable_time_tracking=true ;;
631
+ database) enable_database=true ;; beads) enable_beads=true ;;
632
+ sops) enable_sops=true ;; security) enable_security=true ;;
633
+ esac
634
+ done
635
+
636
+ # Determine init_scope: minimal | standard | public
637
+ # Infer from context when not set; user can override via repos.json or .aidevops.json
638
+ local init_scope
639
+ init_scope=$(_infer_init_scope "$project_root")
640
+ print_info "Init scope: $init_scope (controls which scaffolding files are created)"
641
+
642
+ # Create .aidevops.json config
643
+ local config_file="$project_root/.aidevops.json"
644
+ local aidevops_version
645
+ aidevops_version=$(get_version)
646
+
647
+ print_info "Creating .aidevops.json..."
648
+ cat >"$config_file" <<EOF
649
+ {
650
+ "version": "$aidevops_version",
651
+ "initialized": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
652
+ "init_scope": "$init_scope",
653
+ "features": {
654
+ "planning": $enable_planning,
655
+ "git_workflow": $enable_git_workflow,
656
+ "code_quality": $enable_code_quality,
657
+ "time_tracking": $enable_time_tracking,
658
+ "database": $enable_database,
659
+ "beads": $enable_beads,
660
+ "sops": $enable_sops,
661
+ "security": $enable_security
662
+ },
663
+ "time_tracking": {
664
+ "enabled": $enable_time_tracking,
665
+ "prompt_on_commit": true,
666
+ "auto_record_branch_start": true
667
+ },
668
+ "database": {
669
+ "enabled": $enable_database,
670
+ "schema_path": "schemas",
671
+ "migrations_path": "migrations",
672
+ "seeds_path": "seeds",
673
+ "auto_generate_migration": true
674
+ },
675
+ "beads": {
676
+ "enabled": $enable_beads,
677
+ "sync_on_commit": false,
678
+ "auto_ready_check": true
679
+ },
680
+ "sops": {
681
+ "enabled": $enable_sops,
682
+ "backend": "age",
683
+ "patterns": ["*.secret.yaml", "*.secret.json", "configs/*.enc.json", "configs/*.enc.yaml"]
684
+ },
685
+ "plugins": []
686
+ }
687
+ EOF
688
+ # Note: plugins array is always present but empty by default.
689
+ # Users add plugins via: aidevops plugin add <repo-url> [--namespace <name>]
690
+ # Schema per plugin entry:
691
+ # {
692
+ # "name": "pro",
693
+ # "repo": "https://github.com/user/aidevops-pro.git",
694
+ # "branch": "main",
695
+ # "namespace": "pro",
696
+ # "enabled": true
697
+ # }
698
+ # Plugins deploy to ~/.aidevops/agents/<namespace>/ (namespaced, no collisions)
699
+ print_success "Created .aidevops.json"
700
+
701
+ # Derive repo name for scaffolding
702
+ # In worktrees, basename gives the worktree dir name (e.g., "repo-chore-foo"),
703
+ # not the actual repo name. Prefer: git remote URL > main worktree basename > cwd basename.
704
+ local repo_name
705
+ local remote_url
706
+ remote_url=$(git -C "$project_root" remote get-url origin 2>/dev/null || true)
707
+ local repo_slug=""
708
+ if [[ -n "$remote_url" ]]; then
709
+ repo_slug=$(echo "$remote_url" | sed 's|.*github\.com[:/]||;s|\.git$||')
710
+ fi
711
+ if [[ -n "$remote_url" ]]; then
712
+ repo_name=$(basename "$remote_url" .git)
713
+ else
714
+ # No remote — try main worktree path (first line of `git worktree list`)
715
+ local main_wt
716
+ main_wt=$(git -C "$project_root" worktree list --porcelain 2>/dev/null | head -1 | sed 's/^worktree //')
717
+ if [[ -n "$main_wt" ]]; then
718
+ repo_name=$(basename "$main_wt")
719
+ else
720
+ repo_name=$(basename "$project_root")
721
+ fi
722
+ fi
723
+
724
+ # Create .agents/ directory for project-specific agent context
725
+ # (The aidevops framework is loaded globally via ~/.aidevops/agents/ — this
726
+ # directory is for project-specific agents, conventions, and architecture docs)
727
+ if [[ -L "$project_root/.agents" ]]; then
728
+ # Migrate legacy symlink to real directory
729
+ rm -f "$project_root/.agents"
730
+ print_info "Removed legacy .agents symlink (framework is loaded globally now)"
731
+ fi
732
+ # Also clean up legacy .agent symlink/directory
733
+ if [[ -L "$project_root/.agent" ]]; then
734
+ rm -f "$project_root/.agent"
735
+ print_info "Removed legacy .agent symlink"
736
+ elif [[ -d "$project_root/.agent" && ! -d "$project_root/.agents" ]]; then
737
+ mv "$project_root/.agent" "$project_root/.agents"
738
+ print_success "Migrated .agent/ -> .agents/ directory"
739
+ fi
740
+
741
+ if [[ ! -d "$project_root/.agents" ]]; then
742
+ mkdir -p "$project_root/.agents"
743
+ print_success "Created .agents/ directory"
744
+ fi
745
+
746
+ # Link .agents/commands/ and .windsurf/workflows/ so Amp (native) and Windsurf
747
+ # (symlinked) can see the aidevops main-agent slash commands.
748
+ _init_scaffold_commands_symlinks "$project_root"
749
+
750
+ # Scaffold or update .agents/AGENTS.md (idempotent — creates if missing,
751
+ # updates Security section if file already exists)
752
+ local _agents_md_existed=false
753
+ [[ -f "$project_root/.agents/AGENTS.md" ]] && _agents_md_existed=true
754
+ scaffold_agents_md "$project_root"
755
+ if [[ "$_agents_md_existed" == "true" ]]; then
756
+ print_success "Updated Security section in .agents/AGENTS.md"
757
+ else
758
+ print_success "Created .agents/AGENTS.md"
759
+ fi
760
+
761
+ # Scaffold root AGENTS.md if missing
762
+ if [[ ! -f "$project_root/AGENTS.md" ]]; then
763
+ cat >"$project_root/AGENTS.md" <<ROOTAGENTSEOF
764
+ # $repo_name
765
+
766
+ <!-- AI-CONTEXT-START -->
767
+
768
+ ## Quick Reference
769
+
770
+ - **Build**: \`# TODO: add build command\`
771
+ - **Test**: \`# TODO: add test command\`
772
+ - **Deploy**: \`# TODO: add deploy command\`
773
+
774
+ ## Project Overview
775
+
776
+ <!-- Brief description of what this project does and why it exists. -->
777
+
778
+ ## Architecture
779
+
780
+ <!-- Key architectural decisions, tech stack, directory structure. -->
781
+
782
+ ## Conventions
783
+
784
+ - Commits: [Conventional Commits](https://www.conventionalcommits.org/)
785
+ - Branches: \`feature/\`, \`bugfix/\`, \`hotfix/\`, \`refactor/\`, \`chore/\`
786
+
787
+ ## Key Files
788
+
789
+ | File | Purpose |
790
+ |------|---------|
791
+ | \`.agents/AGENTS.md\` | Project-specific agent instructions |
792
+ | \`TODO.md\` | Task tracking |
793
+ | \`CHANGELOG.md\` | Version history |
794
+
795
+ <!-- AI-CONTEXT-END -->
796
+ ROOTAGENTSEOF
797
+ print_success "Created AGENTS.md"
798
+ fi
799
+
800
+ # Create planning files if enabled
801
+ if [[ "$enable_planning" == "true" ]]; then
802
+ print_info "Setting up planning files..."
803
+
804
+ # Create TODO.md from template
805
+ if [[ ! -f "$project_root/TODO.md" ]]; then
806
+ if [[ -f "$AGENTS_DIR/templates/todo-template.md" ]]; then
807
+ cp "$AGENTS_DIR/templates/todo-template.md" "$project_root/TODO.md"
808
+ print_success "Created TODO.md"
809
+ else
810
+ # Fallback minimal template
811
+ cat >"$project_root/TODO.md" <<'EOF'
812
+ # TODO
813
+
814
+ ## In Progress
815
+
816
+ <!-- Tasks currently being worked on -->
817
+
818
+ ## Backlog
819
+
820
+ <!-- Prioritized list of upcoming tasks -->
821
+
822
+ ---
823
+
824
+ *Format: `- [ ] Task description @owner #tag ~estimate`*
825
+ *Time tracking: `started:`, `completed:`, `actual:`*
826
+ EOF
827
+ print_success "Created TODO.md (minimal template)"
828
+ fi
829
+ else
830
+ print_warning "TODO.md already exists, skipping"
831
+ fi
832
+
833
+ # Create todo/ directory and PLANS.md
834
+ mkdir -p "$project_root/todo/tasks"
835
+
836
+ if [[ ! -f "$project_root/todo/PLANS.md" ]]; then
837
+ if [[ -f "$AGENTS_DIR/templates/plans-template.md" ]]; then
838
+ cp "$AGENTS_DIR/templates/plans-template.md" "$project_root/todo/PLANS.md"
839
+ print_success "Created todo/PLANS.md"
840
+ else
841
+ # Fallback minimal template
842
+ cat >"$project_root/todo/PLANS.md" <<'EOF'
843
+ # Execution Plans
844
+
845
+ Complex, multi-session work that requires detailed planning.
846
+
847
+ ## Active Plans
848
+
849
+ <!-- Plans currently in progress -->
850
+
851
+ ## Completed Plans
852
+
853
+ <!-- Archived completed plans -->
854
+
855
+ ---
856
+
857
+ *See `.agents/workflows/plans.md` for planning workflow*
858
+ EOF
859
+ print_success "Created todo/PLANS.md (minimal template)"
860
+ fi
861
+ else
862
+ print_warning "todo/PLANS.md already exists, skipping"
863
+ fi
864
+
865
+ # Create .gitkeep in tasks
866
+ touch "$project_root/todo/tasks/.gitkeep"
867
+
868
+ # Seed mission-control starter template for personal/org control repos
869
+ local init_actor=""
870
+ if command -v gh &>/dev/null; then
871
+ init_actor=$(gh api user --jq '.login' 2>/dev/null || echo "")
872
+ fi
873
+ local mission_scope=""
874
+ mission_scope=$(_resolve_mission_control_scope "$repo_slug" "$init_actor" 2>/dev/null || echo "")
875
+ _seed_mission_control_template "$project_root" "$mission_scope"
876
+ fi
877
+
878
+ # Create database directories if enabled
879
+ if [[ "$enable_database" == "true" ]]; then
880
+ print_info "Setting up database schema directories..."
881
+
882
+ # Create schemas directory with AGENTS.md
883
+ if [[ ! -d "$project_root/schemas" ]]; then
884
+ mkdir -p "$project_root/schemas"
885
+ cat >"$project_root/schemas/AGENTS.md" <<'EOF'
886
+ # Database Schemas
887
+
888
+ Declarative schema files - source of truth for database structure.
889
+
890
+ See: `@sql-migrations` or `.agents/workflows/sql-migrations.md`
891
+ EOF
892
+ print_success "Created schemas/ directory"
893
+ else
894
+ print_warning "schemas/ already exists, skipping"
895
+ fi
896
+
897
+ # Create migrations directory with AGENTS.md
898
+ if [[ ! -d "$project_root/migrations" ]]; then
899
+ mkdir -p "$project_root/migrations"
900
+ cat >"$project_root/migrations/AGENTS.md" <<'EOF'
901
+ # Database Migrations
902
+
903
+ Auto-generated versioned migration files. Do not edit manually.
904
+
905
+ See: `@sql-migrations` or `.agents/workflows/sql-migrations.md`
906
+ EOF
907
+ print_success "Created migrations/ directory"
908
+ else
909
+ print_warning "migrations/ already exists, skipping"
910
+ fi
911
+
912
+ # Create seeds directory with AGENTS.md
913
+ if [[ ! -d "$project_root/seeds" ]]; then
914
+ mkdir -p "$project_root/seeds"
915
+ cat >"$project_root/seeds/AGENTS.md" <<'EOF'
916
+ # Database Seeds
917
+
918
+ Initial and reference data (roles, statuses, test accounts).
919
+
920
+ See: `@sql-migrations` or `.agents/workflows/sql-migrations.md`
921
+ EOF
922
+ print_success "Created seeds/ directory"
923
+ else
924
+ print_warning "seeds/ already exists, skipping"
925
+ fi
926
+ fi
927
+
928
+ # Initialize Beads if enabled
929
+ if [[ "$enable_beads" == "true" ]]; then
930
+ print_info "Setting up Beads task graph..."
931
+
932
+ # Check if Beads CLI is installed
933
+ if ! command -v bd &>/dev/null; then
934
+ print_warning "Beads CLI (bd) not installed"
935
+ echo " Install with: brew install steveyegge/beads/bd"
936
+ echo " Or download: https://github.com/steveyegge/beads/releases"
937
+ echo " Or via Go: go install github.com/steveyegge/beads/cmd/bd@latest"
938
+ else
939
+ # Initialize Beads in the project
940
+ if [[ ! -d "$project_root/.beads" ]]; then
941
+ print_info "Initializing Beads database..."
942
+ if (cd "$project_root" && bd init 2>/dev/null); then
943
+ print_success "Beads initialized"
944
+ else
945
+ print_warning "Beads init failed - run manually: bd init"
946
+ fi
947
+ else
948
+ print_info "Beads already initialized"
949
+ fi
950
+
951
+ # Run initial sync from TODO.md/PLANS.md
952
+ if [[ -f "$AGENTS_DIR/scripts/beads-sync-helper.sh" ]]; then
953
+ print_info "Syncing tasks to Beads..."
954
+ if bash "$AGENTS_DIR/scripts/beads-sync-helper.sh" push "$project_root" 2>/dev/null; then
955
+ print_success "Tasks synced to Beads"
956
+ else
957
+ print_warning "Beads sync failed - run manually: beads-sync-helper.sh push"
958
+ fi
959
+ fi
960
+ fi
961
+ fi
962
+
963
+ # Initialize SOPS if enabled
964
+ if [[ "$enable_sops" == "true" ]]; then
965
+ print_info "Setting up SOPS encrypted config support..."
966
+
967
+ # Check for sops and age
968
+ local sops_ready=true
969
+ if ! command -v sops &>/dev/null; then
970
+ print_warning "SOPS not installed"
971
+ echo " Install with: brew install sops"
972
+ sops_ready=false
973
+ fi
974
+ if ! command -v age-keygen &>/dev/null; then
975
+ print_warning "age not installed (default SOPS backend)"
976
+ echo " Install with: brew install age"
977
+ sops_ready=false
978
+ fi
979
+
980
+ # Generate age key if none exists
981
+ local age_key_file="$HOME/.config/sops/age/keys.txt"
982
+ if [[ "$sops_ready" == "true" ]] && [[ ! -f "$age_key_file" ]]; then
983
+ print_info "Generating age key for SOPS..."
984
+ mkdir -p "$(dirname "$age_key_file")"
985
+ age-keygen -o "$age_key_file" 2>/dev/null
986
+ chmod 600 "$age_key_file"
987
+ print_success "Age key generated at $age_key_file"
988
+ fi
989
+
990
+ # Create .sops.yaml if it doesn't exist
991
+ if [[ ! -f "$project_root/.sops.yaml" ]]; then
992
+ local age_pubkey=""
993
+ if [[ -f "$age_key_file" ]]; then
994
+ age_pubkey=$(grep -o 'age1[a-z0-9]*' "$age_key_file" | head -1)
995
+ fi
996
+
997
+ if [[ -n "$age_pubkey" ]]; then
998
+ cat >"$project_root/.sops.yaml" <<SOPSEOF
999
+ # SOPS configuration - encrypts values in config files while keeping keys visible
1000
+ # See: .agents/tools/credentials/sops.md
1001
+ creation_rules:
1002
+ - path_regex: '\.secret\.(yaml|yml|json)$'
1003
+ age: >-
1004
+ $age_pubkey
1005
+ - path_regex: 'configs/.*\.enc\.(yaml|yml|json)$'
1006
+ age: >-
1007
+ $age_pubkey
1008
+ SOPSEOF
1009
+ print_success "Created .sops.yaml with age key"
1010
+ else
1011
+ cat >"$project_root/.sops.yaml" <<'SOPSEOF'
1012
+ # SOPS configuration - encrypts values in config files while keeping keys visible
1013
+ # See: .agents/tools/credentials/sops.md
1014
+ #
1015
+ # Generate an age key first:
1016
+ # age-keygen -o ~/.config/sops/age/keys.txt
1017
+ #
1018
+ # Then replace AGE_PUBLIC_KEY below with your public key:
1019
+ creation_rules:
1020
+ - path_regex: '\.secret\.(yaml|yml|json)$'
1021
+ age: >-
1022
+ AGE_PUBLIC_KEY
1023
+ - path_regex: 'configs/.*\.enc\.(yaml|yml|json)$'
1024
+ age: >-
1025
+ AGE_PUBLIC_KEY
1026
+ SOPSEOF
1027
+ print_warning "Created .sops.yaml template (replace AGE_PUBLIC_KEY with your key)"
1028
+ fi
1029
+ else
1030
+ print_info ".sops.yaml already exists"
1031
+ fi
1032
+ fi
1033
+
1034
+ # Ensure .gitattributes has ai-training=false (opt out of AI model training)
1035
+ # GitHub and other platforms respect this attribute to exclude repo content
1036
+ # from AI/ML training datasets. Idempotent — only adds if not already present.
1037
+ local gitattributes="$project_root/.gitattributes"
1038
+ if [[ -f "$gitattributes" ]]; then
1039
+ if ! grep -qE '^\*[[:space:]]+ai-training=false' "$gitattributes" 2>/dev/null; then
1040
+ ensure_trailing_newline "$gitattributes"
1041
+ {
1042
+ echo ""
1043
+ echo "# Opt out of AI model training"
1044
+ echo "* ai-training=false"
1045
+ } >>"$gitattributes"
1046
+ print_success "Added ai-training=false to .gitattributes"
1047
+ else
1048
+ print_info ".gitattributes already has ai-training=false"
1049
+ fi
1050
+ else
1051
+ cat >"$gitattributes" <<'GITATTRSEOF'
1052
+ # Opt out of AI model training
1053
+ * ai-training=false
1054
+ GITATTRSEOF
1055
+ print_success "Created .gitattributes with ai-training=false"
1056
+ fi
1057
+
1058
+ # Add aidevops runtime artifacts to .gitignore
1059
+ # Note: .agents/ itself is NOT ignored — it contains committed project-specific agents.
1060
+ # Only runtime artifacts (loop state, tmp, memory) are ignored.
1061
+ local gitignore="$project_root/.gitignore"
1062
+ if [[ -f "$gitignore" ]]; then
1063
+ local gitignore_updated=false
1064
+
1065
+ # Remove legacy bare ".agents" entry if present (was added by older versions)
1066
+ if grep -q "^\.agents$" "$gitignore" 2>/dev/null; then
1067
+ sed -i '' '/^\.agents$/d' "$gitignore" 2>/dev/null ||
1068
+ sed -i '/^\.agents$/d' "$gitignore" 2>/dev/null || true
1069
+ # Also remove the "# aidevops" comment if it's now orphaned
1070
+ sed -i '' '/^# aidevops$/{ N; /^# aidevops\n$/d; }' "$gitignore" 2>/dev/null || true
1071
+ print_info "Removed legacy bare .agents from .gitignore (now tracked)"
1072
+ gitignore_updated=true
1073
+ fi
1074
+
1075
+ # Remove legacy bare ".agent" entry if present
1076
+ if grep -q "^\.agent$" "$gitignore" 2>/dev/null; then
1077
+ sed -i '' '/^\.agent$/d' "$gitignore" 2>/dev/null ||
1078
+ sed -i '/^\.agent$/d' "$gitignore" 2>/dev/null || true
1079
+ gitignore_updated=true
1080
+ fi
1081
+
1082
+ # Add runtime artifact ignores
1083
+ if ! grep -q "^\.agents/loop-state/" "$gitignore" 2>/dev/null; then
1084
+ # Ensure trailing newline before appending (prevents malformed entries like *.zip.agents/loop-state/)
1085
+ ensure_trailing_newline "$gitignore"
1086
+ {
1087
+ echo ""
1088
+ echo "# aidevops runtime artifacts"
1089
+ echo ".agents/loop-state/"
1090
+ echo ".agents/tmp/"
1091
+ echo ".agents/memory/"
1092
+ } >>"$gitignore"
1093
+ print_success "Added .agents/ runtime artifact ignores to .gitignore"
1094
+ gitignore_updated=true
1095
+ fi
1096
+
1097
+ # Add .aidevops.json to gitignore (local config, not committed).
1098
+ # If .aidevops.json is already tracked by git (committed by older framework
1099
+ # versions), untrack it first — adding a tracked file to .gitignore is a
1100
+ # no-op and the file keeps showing in git diff on every re-init (#2570 bug 3).
1101
+ if ! grep -q "^\.aidevops\.json$" "$gitignore" 2>/dev/null; then
1102
+ if git -C "$project_root" ls-files --error-unmatch .aidevops.json &>/dev/null; then
1103
+ git -C "$project_root" rm --cached .aidevops.json &>/dev/null || true
1104
+ print_info "Untracked .aidevops.json from git (was committed by older version)"
1105
+ fi
1106
+ # Ensure trailing newline before appending
1107
+ ensure_trailing_newline "$gitignore"
1108
+ echo ".aidevops.json" >>"$gitignore"
1109
+ gitignore_updated=true
1110
+ fi
1111
+
1112
+ # Add .beads if beads is enabled
1113
+ if [[ "$enable_beads" == "true" ]]; then
1114
+ if ! grep -q "^\.beads$" "$gitignore" 2>/dev/null; then
1115
+ # Ensure trailing newline before appending
1116
+ ensure_trailing_newline "$gitignore"
1117
+ echo ".beads" >>"$gitignore"
1118
+ print_success "Added .beads to .gitignore"
1119
+ gitignore_updated=true
1120
+ fi
1121
+ fi
1122
+
1123
+ if [[ "$gitignore_updated" == "true" ]]; then
1124
+ print_info "Updated .gitignore"
1125
+ fi
1126
+ fi
1127
+
1128
+ # Scaffold optional files gated by init_scope (collaborator pointers,
1129
+ # DESIGN.md, courtesy files, MODELS.md). Extracted to reduce cmd_init
1130
+ # nesting depth and function length (t2265).
1131
+ _init_scaffold_scope_gated_files "$project_root" "$init_scope" "$repo_name"
1132
+
1133
+ # ─── Badge initialization (t2975) ────────────────────────────────────────
1134
+ # Install the loc-badge caller workflow and seed the canonical README badge
1135
+ # block in fresh repos. Both operations are idempotent. Skip for local_only
1136
+ # repos (no remote to host SVGs or run GitHub Actions).
1137
+ local _badges_helper="$AGENTS_DIR/scripts/readme-badges-helper.sh"
1138
+ local _wf_template="$AGENTS_DIR/templates/workflows/loc-badge-caller.yml"
1139
+ local _wf_dest="$project_root/.github/workflows/loc-badge.yml"
1140
+
1141
+ if [[ -n "$repo_slug" ]]; then
1142
+ # Install loc-badge caller workflow if template is available and file is absent
1143
+ if [[ -f "$_wf_template" && ! -f "$_wf_dest" ]]; then
1144
+ mkdir -p "$project_root/.github/workflows"
1145
+ cp "$_wf_template" "$_wf_dest"
1146
+ print_success "Installed .github/workflows/loc-badge.yml (LOC badge workflow)"
1147
+ elif [[ -f "$_wf_dest" ]]; then
1148
+ print_info ".github/workflows/loc-badge.yml already present"
1149
+ fi
1150
+
1151
+ # Seed the canonical badge block in README.md
1152
+ local _readme_path="$project_root/README.md"
1153
+ if [[ -f "$_badges_helper" && -f "$_readme_path" ]]; then
1154
+ if bash "$_badges_helper" inject "$_readme_path" "$repo_slug" 2>/dev/null; then
1155
+ print_success "Seeded canonical badge block in README.md"
1156
+ else
1157
+ print_warning "Badge block injection failed — run manually: aidevops badges sync --repo $repo_slug --apply"
1158
+ fi
1159
+ elif [[ ! -f "$_readme_path" ]]; then
1160
+ print_info "No README.md found — skipping badge block injection (create README.md first)"
1161
+ fi
1162
+
1163
+ # Remind about SYNC_PAT if the repo has a remote and isn't local_only
1164
+ local _is_local_only
1165
+ _is_local_only=$(jq -r --arg s "$repo_slug" \
1166
+ '.initialized_repos // [] | map(select(.slug == $s)) | if length > 0 then .[0].local_only // false else false end' \
1167
+ "$HOME/.config/aidevops/repos.json" 2>/dev/null || echo "false")
1168
+ if [[ "$_is_local_only" != "true" ]]; then
1169
+ print_info "Reminder: set SYNC_PAT secret so GitHub Actions can push badge SVGs — see: aidevops --help sync-pat"
1170
+ fi
1171
+ fi
1172
+
1173
+ # Run security posture assessment if enabled (t1412.11)
1174
+ if [[ "$enable_security" == "true" ]]; then
1175
+ local security_posture_script="$AGENTS_DIR/scripts/security-posture-helper.sh"
1176
+ if [[ -f "$security_posture_script" ]]; then
1177
+ print_info "Running security posture assessment..."
1178
+ if bash "$security_posture_script" store "$project_root"; then
1179
+ print_success "Security posture assessed and stored in .aidevops.json"
1180
+ else
1181
+ print_warning "Security posture assessment found issues (review with: aidevops security audit)"
1182
+ fi
1183
+ else
1184
+ print_info "Security posture check skipped (security-posture-helper.sh not available)"
1185
+ fi
1186
+ fi
1187
+
1188
+ # Build features string for registration
1189
+ local features_list=""
1190
+ [[ "$enable_planning" == "true" ]] && features_list="${features_list}planning,"
1191
+ [[ "$enable_git_workflow" == "true" ]] && features_list="${features_list}git-workflow,"
1192
+ [[ "$enable_code_quality" == "true" ]] && features_list="${features_list}code-quality,"
1193
+ [[ "$enable_time_tracking" == "true" ]] && features_list="${features_list}time-tracking,"
1194
+ [[ "$enable_database" == "true" ]] && features_list="${features_list}database,"
1195
+ [[ "$enable_beads" == "true" ]] && features_list="${features_list}beads,"
1196
+ [[ "$enable_sops" == "true" ]] && features_list="${features_list}sops,"
1197
+ [[ "$enable_security" == "true" ]] && features_list="${features_list}security,"
1198
+ features_list="${features_list%,}" # Remove trailing comma
1199
+
1200
+ # Register the *main* repo path (not the worktree path) in repos.json.
1201
+ # When check_protected_branch creates a worktree and cd's into it,
1202
+ # $project_root (resolved via git rev-parse --show-toplevel) points to the
1203
+ # worktree directory. We must register the canonical main worktree path so
1204
+ # that pulse and cleanup processes don't treat the worktree as a standalone repo.
1205
+ local register_path="$project_root"
1206
+ if [[ -n "${WORKTREE_PATH:-}" ]]; then
1207
+ # We're inside a worktree — resolve the main worktree path from git metadata
1208
+ local main_wt_path
1209
+ main_wt_path=$(git -C "$project_root" worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2; exit}')
1210
+ if [[ -n "$main_wt_path" ]] && [[ "$main_wt_path" != "$project_root" ]]; then
1211
+ register_path="$main_wt_path"
1212
+ fi
1213
+ fi
1214
+ register_repo "$register_path" "$aidevops_version" "$features_list"
1215
+
1216
+ # Auto-commit initialized files so they don't linger as mystery unstaged
1217
+ # changes (#2570 bug 2). Collect all files that cmd_init creates/modifies.
1218
+ local init_files=()
1219
+ [[ -f "$project_root/.gitattributes" ]] && init_files+=(".gitattributes")
1220
+ [[ -f "$project_root/.gitignore" ]] && init_files+=(".gitignore")
1221
+ [[ -d "$project_root/.agents" ]] && init_files+=(".agents/")
1222
+ [[ -f "$project_root/AGENTS.md" ]] && init_files+=("AGENTS.md")
1223
+ [[ -f "$project_root/DESIGN.md" ]] && init_files+=("DESIGN.md")
1224
+ [[ -f "$project_root/TODO.md" ]] && init_files+=("TODO.md")
1225
+ [[ -d "$project_root/todo" ]] && init_files+=("todo/")
1226
+ [[ -f "$project_root/MODELS.md" ]] && init_files+=("MODELS.md")
1227
+ [[ -f "$project_root/LICENCE" ]] && init_files+=("LICENCE")
1228
+ [[ -f "$project_root/CHANGELOG.md" ]] && init_files+=("CHANGELOG.md")
1229
+ [[ -f "$project_root/README.md" ]] && init_files+=("README.md")
1230
+ [[ -f "$project_root/.cursorrules" ]] && init_files+=(".cursorrules")
1231
+ [[ -f "$project_root/.windsurfrules" ]] && init_files+=(".windsurfrules")
1232
+ [[ -f "$project_root/.clinerules" ]] && init_files+=(".clinerules")
1233
+ [[ -d "$project_root/.github" ]] && init_files+=(".github/")
1234
+ [[ -f "$project_root/.sops.yaml" ]] && init_files+=(".sops.yaml")
1235
+ [[ -d "$project_root/schemas" ]] && init_files+=("schemas/")
1236
+ [[ -d "$project_root/migrations" ]] && init_files+=("migrations/")
1237
+ [[ -d "$project_root/seeds" ]] && init_files+=("seeds/")
1238
+
1239
+ local committed=false
1240
+ if [[ ${#init_files[@]} -gt 0 ]]; then
1241
+ # Stage all init files (--force not needed; .aidevops.json is gitignored above)
1242
+ if git -C "$project_root" add -- "${init_files[@]}" 2>/dev/null; then
1243
+ # Only commit if there are staged changes
1244
+ if ! git -C "$project_root" diff --cached --quiet 2>/dev/null; then
1245
+ if git -C "$project_root" commit -m "chore: initialize aidevops v${aidevops_version}" 2>/dev/null; then
1246
+ committed=true
1247
+ print_success "Committed initialized files"
1248
+ else
1249
+ print_warning "Auto-commit failed (pre-commit hook rejected?)"
1250
+ fi
1251
+ fi
1252
+ fi
1253
+ fi
1254
+
1255
+ echo ""
1256
+ print_success "AI DevOps initialized! (scope: $init_scope)"
1257
+ echo ""
1258
+ echo "Enabled features:"
1259
+ [[ "$enable_planning" == "true" ]] && echo " ✓ Planning (TODO.md, PLANS.md)"
1260
+ [[ "$enable_git_workflow" == "true" ]] && echo " ✓ Git workflow (branch management)"
1261
+ [[ "$enable_code_quality" == "true" ]] && echo " ✓ Code quality (linting, auditing)"
1262
+ [[ "$enable_time_tracking" == "true" ]] && echo " ✓ Time tracking (estimates, actuals)"
1263
+ [[ "$enable_database" == "true" ]] && echo " ✓ Database (schemas/, migrations/, seeds/)"
1264
+ [[ "$enable_beads" == "true" ]] && echo " ✓ Beads (task graph visualization)"
1265
+ [[ "$enable_sops" == "true" ]] && echo " ✓ SOPS (encrypted config files with age backend)"
1266
+ [[ "$enable_security" == "true" ]] && echo " ✓ Security (per-repo posture assessment)"
1267
+ [[ -f "$project_root/MODELS.md" ]] && echo " ✓ MODELS.md (per-repo model performance leaderboard)"
1268
+ echo ""
1269
+ # When init ran inside a worktree (check_protected_branch created one),
1270
+ # print explicit instructions so the user knows where to find their work.
1271
+ # Without this, the user's shell is back in the main repo after aidevops exits
1272
+ # and the worktree appears to have "disappeared".
1273
+ if [[ -n "${WORKTREE_PATH:-}" ]]; then
1274
+ local worktree_branch
1275
+ worktree_branch=$(git branch --show-current 2>/dev/null || echo "chore/aidevops-init")
1276
+ echo "Worktree location:"
1277
+ echo " $WORKTREE_PATH"
1278
+ echo ""
1279
+ echo "Your init commit is in the worktree above. To continue:"
1280
+ echo " cd $WORKTREE_PATH"
1281
+ echo " git push -u origin ${worktree_branch}"
1282
+ echo " gh pr create --fill" # aidevops-allow: raw-gh-wrapper
1283
+ echo ""
1284
+ fi
1285
+ echo "Next steps:"
1286
+ local step=1
1287
+ if [[ "$committed" != "true" ]]; then
1288
+ echo " ${step}. Commit the initialized files: git add -A && git commit -m 'chore: initialize aidevops'"
1289
+ ((++step))
1290
+ fi
1291
+ if [[ "$enable_beads" == "true" ]]; then
1292
+ echo " ${step}. Add tasks to TODO.md with dependencies (blocked-by:t001)"
1293
+ ((++step))
1294
+ echo " ${step}. Run /ready to see unblocked tasks"
1295
+ ((++step))
1296
+ echo " ${step}. Run /sync-beads to sync with Beads graph"
1297
+ ((++step))
1298
+ echo " ${step}. Use 'bd' CLI for graph visualization"
1299
+ elif [[ "$enable_database" == "true" ]]; then
1300
+ echo " ${step}. Add schema files to schemas/"
1301
+ ((++step))
1302
+ echo " ${step}. Run diff to generate migrations"
1303
+ ((++step))
1304
+ echo " ${step}. See .agents/workflows/sql-migrations.md"
1305
+ else
1306
+ echo " ${step}. Add tasks to TODO.md"
1307
+ ((++step))
1308
+ echo " ${step}. Use /create-prd for complex features"
1309
+ ((++step))
1310
+ echo " ${step}. Use /feature to start development"
1311
+ fi
1312
+
1313
+ return 0
1314
+ }