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