chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Common utilities for ArcKit scripts (plugin version).
|
||||
Looks for projects/ directory as repo root indicator.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# ANSI color codes
|
||||
RED = "\033[0;31m"
|
||||
GREEN = "\033[0;32m"
|
||||
YELLOW = "\033[1;33m"
|
||||
BLUE = "\033[0;34m"
|
||||
NC = "\033[0m" # No Color
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Logging Functions
|
||||
# ============================================================================
|
||||
|
||||
def log_info(msg):
|
||||
print(f"{BLUE}[INFO]{NC} {msg}", file=sys.stderr)
|
||||
|
||||
|
||||
def log_success(msg):
|
||||
print(f"{GREEN}[SUCCESS]{NC} {msg}", file=sys.stderr)
|
||||
|
||||
|
||||
def log_warning(msg):
|
||||
print(f"{YELLOW}[WARNING]{NC} {msg}", file=sys.stderr)
|
||||
|
||||
|
||||
def log_error(msg):
|
||||
print(f"{RED}[ERROR]{NC} {msg}", file=sys.stderr)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Repository Root Detection
|
||||
# ============================================================================
|
||||
|
||||
def find_repo_root(start_dir=None):
|
||||
"""Find the repository root by looking for projects/ directory."""
|
||||
current = Path(start_dir or os.getcwd()).resolve()
|
||||
while current != current.parent:
|
||||
if (current / "projects").is_dir():
|
||||
return str(current)
|
||||
current = current.parent
|
||||
log_error("Not in an ArcKit project (no projects/ directory found)")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Project Management
|
||||
# ============================================================================
|
||||
|
||||
def get_next_project_number(repo_root):
|
||||
"""Get the next available project number (zero-padded to 3 digits)."""
|
||||
projects_dir = Path(repo_root) / "projects"
|
||||
if not projects_dir.is_dir():
|
||||
return "001"
|
||||
|
||||
max_num = 0
|
||||
for entry in projects_dir.iterdir():
|
||||
if entry.is_dir():
|
||||
m = re.match(r"^(\d{3})-", entry.name)
|
||||
if m:
|
||||
num = int(m.group(1))
|
||||
if num > max_num:
|
||||
max_num = num
|
||||
|
||||
return f"{max_num + 1:03d}"
|
||||
|
||||
|
||||
def slugify(text):
|
||||
"""Convert text to kebab-case slug."""
|
||||
text = text.lower()
|
||||
text = re.sub(r"[^a-z0-9]+", "-", text)
|
||||
text = text.strip("-")
|
||||
return text
|
||||
|
||||
|
||||
def create_project_dir(project_dir):
|
||||
"""Create project directory structure with all required subdirectories."""
|
||||
subdirs = [
|
||||
"", "vendors", "external", "final",
|
||||
"decisions", "diagrams", "wardley-maps",
|
||||
"data-contracts", "reviews",
|
||||
]
|
||||
for sub in subdirs:
|
||||
(Path(project_dir) / sub).mkdir(parents=True, exist_ok=True)
|
||||
log_success(f"Created project directory: {project_dir}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Project Finding
|
||||
# ============================================================================
|
||||
|
||||
def find_project_dir_by_prefix(prefix, repo_root=None):
|
||||
"""Find project directory by number or prefix (exact then fuzzy match)."""
|
||||
if repo_root is None:
|
||||
repo_root = find_repo_root()
|
||||
projects_dir = Path(repo_root) / "projects"
|
||||
if not projects_dir.is_dir():
|
||||
log_error("No projects directory found")
|
||||
return None
|
||||
|
||||
# Exact match first
|
||||
for entry in sorted(projects_dir.iterdir()):
|
||||
if entry.is_dir():
|
||||
name = entry.name
|
||||
if name == prefix or name.startswith(f"{prefix}-"):
|
||||
return str(entry)
|
||||
|
||||
# Fuzzy match
|
||||
for entry in sorted(projects_dir.iterdir()):
|
||||
if entry.is_dir() and prefix in entry.name:
|
||||
return str(entry)
|
||||
|
||||
log_error(f"No project found matching: {prefix}")
|
||||
return None
|
||||
|
||||
|
||||
def get_project_number_from_dir(dir_path):
|
||||
"""Extract 3-digit project number from directory name."""
|
||||
name = Path(dir_path).name
|
||||
m = re.match(r"^(\d{3})-", name)
|
||||
return m.group(1) if m else None
|
||||
|
||||
|
||||
def list_projects(repo_root=None):
|
||||
"""List all projects."""
|
||||
if repo_root is None:
|
||||
repo_root = find_repo_root()
|
||||
projects_dir = Path(repo_root) / "projects"
|
||||
if not projects_dir.is_dir():
|
||||
print("No projects found")
|
||||
return
|
||||
|
||||
dirs = sorted(d for d in projects_dir.iterdir() if d.is_dir())
|
||||
if not dirs:
|
||||
print("No projects found")
|
||||
return
|
||||
|
||||
print("Available projects:")
|
||||
for d in dirs:
|
||||
print(f" - {d.name}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Git Integration
|
||||
# ============================================================================
|
||||
|
||||
def has_git():
|
||||
"""Check if git is available."""
|
||||
return shutil.which("git") is not None
|
||||
|
||||
|
||||
def get_repo_root():
|
||||
"""Get repository root using git (fallback to find_repo_root)."""
|
||||
if has_git():
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||
pass
|
||||
return find_repo_root()
|
||||
|
||||
|
||||
def get_current_branch():
|
||||
"""Get current git branch."""
|
||||
if has_git():
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||
pass
|
||||
return "main"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Validation Helpers
|
||||
# ============================================================================
|
||||
|
||||
def check_file(file_path, description=None):
|
||||
"""Check if file exists and print status."""
|
||||
if description is None:
|
||||
description = Path(file_path).name
|
||||
if Path(file_path).is_file():
|
||||
print(f" \u2713 {description}")
|
||||
return True
|
||||
print(f" \u2717 {description}")
|
||||
return False
|
||||
|
||||
|
||||
def check_dir(dir_path, description=None):
|
||||
"""Check if directory exists and is not empty."""
|
||||
if description is None:
|
||||
description = Path(dir_path).name
|
||||
p = Path(dir_path)
|
||||
if p.is_dir() and any(p.iterdir()):
|
||||
print(f" \u2713 {description}")
|
||||
return True
|
||||
print(f" \u2717 {description}")
|
||||
return False
|
||||
|
||||
|
||||
def require_file(file_path, description=None):
|
||||
"""Require file to exist."""
|
||||
if description is None:
|
||||
description = Path(file_path).name
|
||||
if not Path(file_path).is_file():
|
||||
log_error(f"Required file not found: {description}")
|
||||
log_error(f" Path: {file_path}")
|
||||
return False
|
||||
log_success(f"Found: {description}")
|
||||
return True
|
||||
|
||||
|
||||
def require_dir(dir_path, description=None):
|
||||
"""Require directory to exist."""
|
||||
if description is None:
|
||||
description = Path(dir_path).name
|
||||
if not Path(dir_path).is_dir():
|
||||
log_error(f"Required directory not found: {description}")
|
||||
log_error(f" Path: {dir_path}")
|
||||
return False
|
||||
log_success(f"Found: {description}")
|
||||
return True
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# JSON Helpers
|
||||
# ============================================================================
|
||||
|
||||
def json_escape(s):
|
||||
"""Escape string for JSON embedding."""
|
||||
return json.dumps(s)[1:-1] # Strip surrounding quotes
|
||||
|
||||
|
||||
def output_json_array(items):
|
||||
"""Output a JSON array string from a list."""
|
||||
return json.dumps(items)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Path Helpers
|
||||
# ============================================================================
|
||||
|
||||
def get_arckit_dir(repo_root=None):
|
||||
"""Get .arckit directory path."""
|
||||
if repo_root is None:
|
||||
repo_root = find_repo_root()
|
||||
return os.path.join(repo_root, ".arckit")
|
||||
|
||||
|
||||
def get_templates_dir(repo_root=None):
|
||||
"""Get templates directory path."""
|
||||
if repo_root is None:
|
||||
repo_root = find_repo_root()
|
||||
return os.path.join(repo_root, ".arckit", "templates")
|
||||
|
||||
|
||||
def get_projects_dir(repo_root=None):
|
||||
"""Get projects directory path."""
|
||||
if repo_root is None:
|
||||
repo_root = find_repo_root()
|
||||
return os.path.join(repo_root, "projects")
|
||||
|
||||
|
||||
def get_memory_dir(repo_root=None):
|
||||
"""Get memory directory path (000-global)."""
|
||||
if repo_root is None:
|
||||
repo_root = find_repo_root()
|
||||
return os.path.join(repo_root, "projects", "000-global")
|
||||
@@ -0,0 +1,373 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Create a new ArcKit project for architecture governance.
|
||||
|
||||
Usage:
|
||||
python3 create-project.py [OPTIONS]
|
||||
|
||||
Options:
|
||||
--name "PROJECT_NAME" Name of the project
|
||||
--json Output JSON for AI agent consumption
|
||||
--force Skip prerequisites check
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path for common imports
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import (
|
||||
find_repo_root, get_next_project_number, slugify, create_project_dir,
|
||||
get_arckit_dir, get_memory_dir, get_templates_dir,
|
||||
log_info, log_success, log_error, output_json_array,
|
||||
)
|
||||
|
||||
|
||||
def has_doc(project_dir, project_number, type_code):
|
||||
"""Check if a document with the given type code exists."""
|
||||
pattern = os.path.join(project_dir, f"ARC-{project_number}-{type_code}-v*.md")
|
||||
return len(glob.glob(pattern)) > 0
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Create a new ArcKit project")
|
||||
parser.add_argument("--name", default="", help="Name of the project")
|
||||
parser.add_argument("--json", dest="output_json", action="store_true", help="Output JSON for AI agent consumption")
|
||||
parser.add_argument("--force", action="store_true", help="Skip prerequisites check")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Find repository root
|
||||
repo_root = find_repo_root()
|
||||
|
||||
# Check prerequisites (unless --force)
|
||||
if not args.force:
|
||||
global_dir = get_memory_dir(repo_root)
|
||||
principles_files = glob.glob(os.path.join(global_dir, "ARC-000-PRIN-*.md"))
|
||||
if not principles_files or not os.path.isfile(principles_files[0]):
|
||||
log_error("Prerequisites not met: Architecture principles not found")
|
||||
log_error("Expected: projects/000-global/ARC-000-PRIN-v*.md")
|
||||
log_error("")
|
||||
log_error("Before creating a project, you must define architecture principles")
|
||||
log_error("Run: /arckit:principles")
|
||||
log_error("")
|
||||
log_error("Or use --force to skip this check (not recommended)")
|
||||
sys.exit(1)
|
||||
log_success("Prerequisites check passed")
|
||||
|
||||
# Get project name
|
||||
project_name = args.name
|
||||
if not project_name:
|
||||
if args.output_json:
|
||||
log_error("Project name is required in JSON mode")
|
||||
print('{"error": "Project name is required", "success": false}')
|
||||
sys.exit(1)
|
||||
log_info("Interactive mode: Creating a new ArcKit project")
|
||||
print()
|
||||
try:
|
||||
project_name = input("Enter project name: ").strip()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print()
|
||||
sys.exit(1)
|
||||
if not project_name:
|
||||
log_error("Project name cannot be empty")
|
||||
sys.exit(1)
|
||||
|
||||
# Get next project number
|
||||
project_number = get_next_project_number(repo_root)
|
||||
log_info(f"Project number: {project_number}")
|
||||
|
||||
# Create project slug and directory
|
||||
project_slug = slugify(project_name)
|
||||
project_dir_name = f"{project_number}-{project_slug}"
|
||||
project_dir = os.path.join(repo_root, "projects", project_dir_name)
|
||||
|
||||
log_info(f"Creating project: {project_dir_name}")
|
||||
|
||||
# Create project directory structure
|
||||
create_project_dir(project_dir)
|
||||
|
||||
# Create README for external documents directory
|
||||
external_readme = os.path.join(project_dir, "external", "README.md")
|
||||
Path(external_readme).write_text("""\
|
||||
# External Documents
|
||||
|
||||
Place external reference documents here for ArcKit commands to read as context.
|
||||
|
||||
## Supported File Types
|
||||
- PDF (.pdf)
|
||||
- Word (.docx)
|
||||
- Markdown (.md)
|
||||
- Images (.png, .jpg) - for diagrams and screenshots
|
||||
- CSV (.csv) - for data exports
|
||||
- Subtitles/transcripts (.srt, .vtt) - for meetings, talks, and recorded walkthroughs
|
||||
- SQL (.sql) - for database schemas
|
||||
|
||||
## What to Put Here
|
||||
- RFP/ITT documents
|
||||
- Legacy system specifications
|
||||
- User research reports
|
||||
- Meeting/video transcripts and subtitle exports
|
||||
- Previous assessments and audits
|
||||
- Database schemas and ERD diagrams
|
||||
- Compliance evidence and certificates
|
||||
- Vendor proposals and technical responses
|
||||
- Performance benchmarks and test results
|
||||
|
||||
## How It Works
|
||||
ArcKit commands automatically scan this directory when generating artifacts.
|
||||
External documents enhance output quality but are never blocking.
|
||||
|
||||
## See Also
|
||||
- `projects/000-global/policies/` - Organization-wide standards and governance documents
|
||||
""")
|
||||
|
||||
# Ensure 000-global/policies exists and has a README
|
||||
global_dir_path = os.path.join(repo_root, "projects", "000-global")
|
||||
if os.path.isdir(global_dir_path):
|
||||
policies_dir = os.path.join(global_dir_path, "policies")
|
||||
os.makedirs(policies_dir, exist_ok=True)
|
||||
policies_readme = os.path.join(policies_dir, "README.md")
|
||||
if not os.path.isfile(policies_readme):
|
||||
Path(policies_readme).write_text("""\
|
||||
# Organization Policies
|
||||
|
||||
Place organization-wide governance documents here. These are read by commands across ALL projects.
|
||||
|
||||
## Supported File Types
|
||||
- PDF (.pdf), Word (.docx), Markdown (.md)
|
||||
|
||||
## What to Put Here
|
||||
- Architecture principles and TOGAF standards
|
||||
- Security policies and compliance frameworks
|
||||
- Risk appetite statements and threat assessments
|
||||
- Technology standards and approved platforms
|
||||
- Procurement policies and spending thresholds
|
||||
- Cloud-first mandates and approved supplier lists
|
||||
- AI governance frameworks and ethical guidelines
|
||||
- MOD/Defence security policies (JSP 440, CAAT)
|
||||
|
||||
## How It Works
|
||||
Commands like /arckit:principles, /arckit:risk, /arckit:secure, and /arckit:sobc
|
||||
automatically scan this directory for organizational context.
|
||||
""")
|
||||
|
||||
# Ensure 000-global/external exists and has a README
|
||||
if os.path.isdir(global_dir_path):
|
||||
global_ext_dir = os.path.join(global_dir_path, "external")
|
||||
os.makedirs(global_ext_dir, exist_ok=True)
|
||||
global_ext_readme = os.path.join(global_ext_dir, "README.md")
|
||||
if not os.path.isfile(global_ext_readme):
|
||||
Path(global_ext_readme).write_text("""\
|
||||
# Global External Documents
|
||||
|
||||
Place organization-wide reference documents here. These are read by commands across ALL projects.
|
||||
|
||||
## Supported File Types
|
||||
- PDF (.pdf), Word (.docx), Markdown (.md)
|
||||
- Images (.png, .jpg) - for diagrams and screenshots
|
||||
- CSV (.csv) - for data exports
|
||||
- Subtitles/transcripts (.srt, .vtt) - for meetings, talks, and recorded walkthroughs
|
||||
- SQL (.sql) - for database schemas
|
||||
|
||||
## What to Put Here
|
||||
- Enterprise architecture blueprints and reference models
|
||||
- Organization-wide technology standards documents
|
||||
- Shared compliance evidence and audit reports
|
||||
- Cross-project strategy and transformation documents
|
||||
- Cross-project meeting/video transcripts and subtitle exports
|
||||
- Industry benchmarks and analyst reports
|
||||
|
||||
## How It Works
|
||||
ArcKit commands automatically scan this directory alongside project-level
|
||||
external documents when generating artifacts.
|
||||
|
||||
## See Also
|
||||
- `projects/000-global/policies/` - Governance policies (risk appetite, security, procurement)
|
||||
- `projects/{NNN}-{name}/external/` - Project-specific reference documents
|
||||
""")
|
||||
|
||||
# Create project README
|
||||
today = date.today().isoformat()
|
||||
project_readme = os.path.join(project_dir, "README.md")
|
||||
Path(project_readme).write_text(f"""\
|
||||
# {project_name}
|
||||
|
||||
Project ID: {project_number}
|
||||
Created: {today}
|
||||
|
||||
## Overview
|
||||
|
||||
[Project description to be added]
|
||||
|
||||
## Workflow
|
||||
|
||||
Use ArcKit commands to generate project artifacts in the recommended order:
|
||||
|
||||
### Discovery Phase
|
||||
1. `/arckit:stakeholders` - Analyze stakeholder drivers and goals
|
||||
2. `/arckit:risk` - Create risk register
|
||||
3. `/arckit:sobc` - Create Strategic Outline Business Case
|
||||
|
||||
### Alpha Phase
|
||||
4. `/arckit:requirements` - Define comprehensive requirements
|
||||
5. `/arckit:data-model` - Design data model and GDPR compliance
|
||||
6. `/arckit:wardley` - Create Wardley maps for strategic planning
|
||||
7. `/arckit:research` - Research technology options (if needed)
|
||||
8. `/arckit:sow` - Generate Statement of Work for vendor procurement (if needed)
|
||||
9. `/arckit:evaluate` - Create vendor evaluation framework (if needed)
|
||||
|
||||
### Beta Phase
|
||||
10. `/arckit:hld-review` - Review High-Level Design
|
||||
11. `/arckit:dld-review` - Review Detailed Design
|
||||
12. `/arckit:traceability` - Generate requirements traceability matrix
|
||||
|
||||
### Compliance (as needed)
|
||||
- `/arckit:secure` - UK Government Secure by Design review
|
||||
- `/arckit:tcop` - Technology Code of Practice assessment
|
||||
- `/arckit:ai-playbook` - AI Playbook compliance (for AI systems)
|
||||
|
||||
## Project Structure
|
||||
|
||||
Documents use standardized naming: `ARC-{{PROJECT_ID}}-{{TYPE}}-v{{VERSION}}.md`
|
||||
|
||||
```
|
||||
{project_dir_name}/
|
||||
\u251c\u2500\u2500 README.md (this file)
|
||||
\u2502
|
||||
\u251c\u2500\u2500 # Core Documents
|
||||
\u251c\u2500\u2500 ARC-{project_number}-STKE-v1.0.md # Stakeholder drivers (/arckit:stakeholders)
|
||||
\u251c\u2500\u2500 ARC-{project_number}-RISK-v1.0.md # Risk register (/arckit:risk)
|
||||
\u251c\u2500\u2500 ARC-{project_number}-SOBC-v1.0.md # Business case (/arckit:sobc)
|
||||
\u251c\u2500\u2500 ARC-{project_number}-REQ-v1.0.md # Requirements (/arckit:requirements)
|
||||
\u251c\u2500\u2500 ARC-{project_number}-DATA-v1.0.md # Data model (/arckit:data-model)
|
||||
\u251c\u2500\u2500 ARC-{project_number}-RSCH-v1.0.md # Research findings (/arckit:research)
|
||||
\u251c\u2500\u2500 ARC-{project_number}-TRAC-v1.0.md # Traceability matrix (/arckit:traceability)
|
||||
\u2502
|
||||
\u251c\u2500\u2500 # Procurement
|
||||
\u251c\u2500\u2500 ARC-{project_number}-SOW-v1.0.md # Statement of Work (/arckit:sow)
|
||||
\u251c\u2500\u2500 ARC-{project_number}-EVAL-v1.0.md # Evaluation criteria (/arckit:evaluate)
|
||||
\u2502
|
||||
\u251c\u2500\u2500 # Multi-instance Documents (subdirectories)
|
||||
\u251c\u2500\u2500 decisions/
|
||||
\u2502 \u251c\u2500\u2500 ARC-{project_number}-ADR-001-v1.0.md # Architecture decisions (/arckit:adr)
|
||||
\u2502 \u2514\u2500\u2500 ARC-{project_number}-ADR-002-v1.0.md
|
||||
\u251c\u2500\u2500 diagrams/
|
||||
\u2502 \u2514\u2500\u2500 ARC-{project_number}-DIAG-001-v1.0.md # Diagrams (/arckit:diagram)
|
||||
\u251c\u2500\u2500 wardley-maps/
|
||||
\u2502 \u2514\u2500\u2500 ARC-{project_number}-WARD-001-v1.0.md # Wardley maps (/arckit:wardley)
|
||||
\u251c\u2500\u2500 reviews/
|
||||
\u2502 \u251c\u2500\u2500 ARC-{project_number}-HLD-v1.0.md # HLD review (/arckit:hld-review)
|
||||
\u2502 \u2514\u2500\u2500 ARC-{project_number}-DLD-v1.0.md # DLD review (/arckit:dld-review)
|
||||
\u2502
|
||||
\u251c\u2500\u2500 external/ # External documents (PDFs, specs, reports)
|
||||
\u2514\u2500\u2500 vendors/ # Vendor proposals
|
||||
```
|
||||
|
||||
## Document Type Codes
|
||||
|
||||
| Code | Document Type |
|
||||
|------|---------------|
|
||||
| REQ | Requirements |
|
||||
| STKE | Stakeholder Analysis |
|
||||
| RISK | Risk Register |
|
||||
| SOBC | Strategic Outline Business Case |
|
||||
| DATA | Data Model |
|
||||
| ADR | Architecture Decision Record |
|
||||
| RSCH | Research Findings |
|
||||
| SOW | Statement of Work |
|
||||
| EVAL | Evaluation Criteria |
|
||||
| HLD | High-Level Design Review |
|
||||
| DLD | Detailed-Level Design Review |
|
||||
| TRAC | Traceability Matrix |
|
||||
| DIAG | Architecture Diagram |
|
||||
| WARD | Wardley Map |
|
||||
| TCOP | Technology Code of Practice |
|
||||
| SECD | Secure by Design |
|
||||
|
||||
## Status
|
||||
|
||||
Track your progress through the workflow:
|
||||
|
||||
**Discovery Phase:**
|
||||
- [ ] Stakeholder analysis complete
|
||||
- [ ] Risk register created
|
||||
- [ ] Business case approved
|
||||
|
||||
**Alpha Phase:**
|
||||
- [ ] Requirements defined
|
||||
- [ ] Data model designed
|
||||
- [ ] Vendor procurement started (if needed)
|
||||
|
||||
**Beta Phase:**
|
||||
- [ ] HLD reviewed and approved
|
||||
- [ ] DLD reviewed and approved
|
||||
- [ ] Traceability matrix validated
|
||||
|
||||
**Live Phase:**
|
||||
- [ ] Implementation complete
|
||||
- [ ] Production deployment
|
||||
""")
|
||||
|
||||
log_success("Project created successfully")
|
||||
|
||||
# Determine next steps
|
||||
next_steps = []
|
||||
if not has_doc(project_dir, project_number, "STKE"):
|
||||
next_steps.append("/arckit:stakeholders - Analyze stakeholder drivers and goals")
|
||||
elif not has_doc(project_dir, project_number, "RISK"):
|
||||
next_steps.append("/arckit:risk - Create risk register")
|
||||
elif not has_doc(project_dir, project_number, "SOBC"):
|
||||
next_steps.append("/arckit:sobc - Create Strategic Outline Business Case")
|
||||
elif not has_doc(project_dir, project_number, "REQ"):
|
||||
next_steps.append("/arckit:requirements - Define business and technical requirements")
|
||||
elif not has_doc(project_dir, project_number, "DATA"):
|
||||
next_steps.append("/arckit:data-model - Design data model")
|
||||
elif not os.path.isdir(os.path.join(project_dir, "wardley-maps")) or \
|
||||
not any(Path(os.path.join(project_dir, "wardley-maps")).iterdir()):
|
||||
next_steps.append("/arckit:research - Research technology options")
|
||||
next_steps.append("/arckit:wardley - Create Wardley maps")
|
||||
elif not has_doc(project_dir, project_number, "SOW"):
|
||||
next_steps.append("/arckit:sow - Generate Statement of Work for RFP")
|
||||
else:
|
||||
next_steps.append("/arckit:evaluate - Create vendor evaluation framework")
|
||||
|
||||
# Output
|
||||
if args.output_json:
|
||||
output = {
|
||||
"success": True,
|
||||
"project_dir": project_dir,
|
||||
"project_number": project_number,
|
||||
"project_name": project_name,
|
||||
"requirements_file": os.path.join(project_dir, f"ARC-{project_number}-REQ-v1.0.md"),
|
||||
"stakeholders_file": os.path.join(project_dir, f"ARC-{project_number}-STKE-v1.0.md"),
|
||||
"risk_file": os.path.join(project_dir, f"ARC-{project_number}-RISK-v1.0.md"),
|
||||
"sobc_file": os.path.join(project_dir, f"ARC-{project_number}-SOBC-v1.0.md"),
|
||||
"sow_file": os.path.join(project_dir, f"ARC-{project_number}-SOW-v1.0.md"),
|
||||
"evaluation_file": os.path.join(project_dir, f"ARC-{project_number}-EVAL-v1.0.md"),
|
||||
"traceability_file": os.path.join(project_dir, f"ARC-{project_number}-TRAC-v1.0.md"),
|
||||
"decisions_dir": os.path.join(project_dir, "decisions"),
|
||||
"diagrams_dir": os.path.join(project_dir, "diagrams"),
|
||||
"wardley_maps_dir": os.path.join(project_dir, "wardley-maps"),
|
||||
"reviews_dir": os.path.join(project_dir, "reviews"),
|
||||
"vendors_dir": os.path.join(project_dir, "vendors"),
|
||||
"external_dir": os.path.join(project_dir, "external"),
|
||||
"global_external_dir": os.path.join(repo_root, "projects", "000-global", "external"),
|
||||
"policies_dir": os.path.join(repo_root, "projects", "000-global", "policies"),
|
||||
"next_steps": next_steps,
|
||||
}
|
||||
print(json.dumps(output, indent=2))
|
||||
else:
|
||||
log_info(f"Project directory: {project_dir}")
|
||||
print()
|
||||
log_info("Next steps:")
|
||||
for i, step in enumerate(next_steps, 1):
|
||||
log_info(f" {i}. {step}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate standardized ArcKit document IDs.
|
||||
|
||||
Usage:
|
||||
python3 generate-document-id.py PROJECT_ID DOC_TYPE [VERSION] [OPTIONS]
|
||||
|
||||
Options:
|
||||
--filename Return ID with .md extension
|
||||
--next-num DIR For multi-instance types, find next sequence number
|
||||
|
||||
Examples:
|
||||
python3 generate-document-id.py 001 REQ -> ARC-001-REQ-v1.0
|
||||
python3 generate-document-id.py 042 HLD 2.1 -> ARC-042-HLD-v2.1
|
||||
python3 generate-document-id.py 001 REQ 1.0 --filename -> ARC-001-REQ-v1.0.md
|
||||
python3 generate-document-id.py 001 ADR 1.0 --filename --next-num ./decisions -> ARC-001-ADR-001-v1.0.md
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Multi-instance document types that require sequence numbers
|
||||
# Keep in sync with arckit-claude/config/doc-types.mjs MULTI_INSTANCE_TYPES
|
||||
MULTI_INSTANCE_TYPES = {"ADR", "DIAG", "DFD", "WARD", "DMC", "RSCH", "AWRS", "AZRS", "GCRS", "DSCT", "WGAM", "WCLM", "WVCH", "GOVR", "GCSR", "GLND"}
|
||||
|
||||
|
||||
def is_multi_instance(doc_type):
|
||||
return doc_type in MULTI_INSTANCE_TYPES
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate standardized ArcKit document IDs",
|
||||
add_help=True,
|
||||
)
|
||||
parser.add_argument("project_id", help="Project ID (e.g., 001)")
|
||||
parser.add_argument("doc_type", help="Document type code (e.g., REQ, ADR)")
|
||||
parser.add_argument("version", nargs="?", default="1.0", help="Version (default: 1.0)")
|
||||
parser.add_argument("--filename", action="store_true", help="Return ID with .md extension")
|
||||
parser.add_argument("--next-num", dest="next_num_dir", metavar="DIR",
|
||||
help="For multi-instance types, scan DIR for next sequence number")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Normalize project ID to 3-digit zero-padded
|
||||
try:
|
||||
pid_clean = int(args.project_id.lstrip("0") or "0")
|
||||
except ValueError:
|
||||
print(f"Error: Invalid PROJECT_ID: {args.project_id}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
padded_pid = f"{pid_clean:03d}"
|
||||
|
||||
doc_type = args.doc_type
|
||||
version = args.version
|
||||
|
||||
if is_multi_instance(doc_type):
|
||||
if args.next_num_dir:
|
||||
next_num_dir = args.next_num_dir
|
||||
if os.path.isdir(next_num_dir):
|
||||
# Scan directory for existing files to find next sequence number
|
||||
pattern_prefix = f"ARC-{padded_pid}-{doc_type}-"
|
||||
last_num = 0
|
||||
for fname in os.listdir(next_num_dir):
|
||||
if not fname.endswith(".md"):
|
||||
continue
|
||||
m = re.match(
|
||||
rf"ARC-{padded_pid}-{re.escape(doc_type)}-(\d+)-",
|
||||
fname,
|
||||
)
|
||||
if m:
|
||||
num = int(m.group(1))
|
||||
if num > last_num:
|
||||
last_num = num
|
||||
next_num = f"{last_num + 1:03d}"
|
||||
else:
|
||||
# Directory doesn't exist yet, start at 001
|
||||
next_num = "001"
|
||||
doc_id = f"ARC-{padded_pid}-{doc_type}-{next_num}-v{version}"
|
||||
else:
|
||||
print(f"Error: Multi-instance type '{doc_type}' requires --next-num DIR option", file=sys.stderr)
|
||||
print(f"Usage: {sys.argv[0]} {args.project_id} {doc_type} {version} --next-num ./directory", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
# Single-instance document type
|
||||
doc_id = f"ARC-{padded_pid}-{doc_type}-v{version}"
|
||||
|
||||
if args.filename:
|
||||
doc_id += ".md"
|
||||
|
||||
print(doc_id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
List all ArcKit projects with status indicators.
|
||||
|
||||
Usage:
|
||||
python3 list-projects.py [OPTIONS]
|
||||
|
||||
Options:
|
||||
--json Output in JSON format
|
||||
--verbose, -v Show detailed artifact status
|
||||
--help, -h Show help message
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path for common imports
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import (
|
||||
find_repo_root, get_projects_dir, get_project_number_from_dir,
|
||||
log_warning,
|
||||
)
|
||||
|
||||
|
||||
def check_artifact(project_dir, artifact):
|
||||
"""Check if an artifact exists (file or non-empty directory)."""
|
||||
if artifact.endswith("/"):
|
||||
d = os.path.join(project_dir, artifact.rstrip("/"))
|
||||
return os.path.isdir(d) and bool(os.listdir(d))
|
||||
return os.path.isfile(os.path.join(project_dir, artifact))
|
||||
|
||||
|
||||
def count_vendors(project_dir):
|
||||
"""Count vendor proposal directories."""
|
||||
vendors_dir = os.path.join(project_dir, "vendors")
|
||||
if not os.path.isdir(vendors_dir):
|
||||
return 0
|
||||
return sum(1 for d in os.listdir(vendors_dir)
|
||||
if os.path.isdir(os.path.join(vendors_dir, d)))
|
||||
|
||||
|
||||
def count_external_docs(project_dir):
|
||||
"""Count external documents (excluding README.md)."""
|
||||
external_dir = os.path.join(project_dir, "external")
|
||||
if not os.path.isdir(external_dir):
|
||||
return 0
|
||||
extensions = {".pdf", ".docx", ".md", ".csv", ".sql", ".png", ".jpg"}
|
||||
count = 0
|
||||
for fname in os.listdir(external_dir):
|
||||
if fname == "README.md":
|
||||
continue
|
||||
fpath = os.path.join(external_dir, fname)
|
||||
if os.path.isfile(fpath) and Path(fname).suffix.lower() in extensions:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def calculate_completion(project_dir):
|
||||
"""Calculate completion percentage based on standard artifacts."""
|
||||
total = 10
|
||||
completed = 0
|
||||
|
||||
artifacts = [
|
||||
"stakeholder-drivers.md", "risk-register.md", "sobc.md",
|
||||
"requirements.md", "data-model.md", "research-findings.md",
|
||||
"sow.md", "evaluation-criteria.md",
|
||||
]
|
||||
|
||||
for artifact in artifacts:
|
||||
if os.path.isfile(os.path.join(project_dir, artifact)):
|
||||
completed += 1
|
||||
|
||||
# Wardley maps
|
||||
wm_dir = os.path.join(project_dir, "wardley-maps")
|
||||
if os.path.isdir(wm_dir) and os.listdir(wm_dir):
|
||||
completed += 1
|
||||
|
||||
# Vendors
|
||||
vendors_dir = os.path.join(project_dir, "vendors")
|
||||
if os.path.isdir(vendors_dir) and os.listdir(vendors_dir):
|
||||
completed += 1
|
||||
|
||||
return completed * 100 // total
|
||||
|
||||
|
||||
def get_status_emoji(percentage):
|
||||
"""Get status indicator based on completion."""
|
||||
if percentage == 100:
|
||||
return "\u2705"
|
||||
elif percentage >= 75:
|
||||
return "\U0001f7e2"
|
||||
elif percentage >= 50:
|
||||
return "\U0001f7e1"
|
||||
elif percentage >= 25:
|
||||
return "\U0001f7e0"
|
||||
else:
|
||||
return "\U0001f534"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="List all ArcKit projects with status indicators")
|
||||
parser.add_argument("--json", dest="json_mode", action="store_true", help="Output in JSON format")
|
||||
parser.add_argument("--verbose", "-v", action="store_true", help="Show detailed artifact status")
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_root = find_repo_root()
|
||||
projects_dir = get_projects_dir(repo_root)
|
||||
|
||||
if not os.path.isdir(projects_dir):
|
||||
if args.json_mode:
|
||||
print('{"projects": []}')
|
||||
else:
|
||||
log_warning("No projects directory found")
|
||||
print()
|
||||
print("Run: /arckit:init to initialize an ArcKit repository")
|
||||
sys.exit(0)
|
||||
|
||||
# Get sorted project directories
|
||||
project_dirs = sorted(
|
||||
d for d in Path(projects_dir).iterdir() if d.is_dir()
|
||||
)
|
||||
project_count = len(project_dirs)
|
||||
|
||||
if project_count == 0:
|
||||
if args.json_mode:
|
||||
print('{"projects": []}')
|
||||
else:
|
||||
print("No projects found")
|
||||
print()
|
||||
print("Run: /arckit:create to create a new project")
|
||||
sys.exit(0)
|
||||
|
||||
# JSON output mode
|
||||
if args.json_mode:
|
||||
projects = []
|
||||
for pd in project_dirs:
|
||||
pdir = str(pd)
|
||||
project_name = pd.name
|
||||
project_number = get_project_number_from_dir(pdir) or ""
|
||||
vendor_count = count_vendors(pdir)
|
||||
external_doc_count = count_external_docs(pdir)
|
||||
completion = calculate_completion(pdir)
|
||||
|
||||
projects.append({
|
||||
"name": project_name,
|
||||
"number": project_number,
|
||||
"path": pdir,
|
||||
"completion_percentage": completion,
|
||||
"vendor_count": vendor_count,
|
||||
"external_doc_count": external_doc_count,
|
||||
"artifacts": {
|
||||
"stakeholder_drivers": check_artifact(pdir, "stakeholder-drivers.md"),
|
||||
"risk_register": check_artifact(pdir, "risk-register.md"),
|
||||
"sobc": check_artifact(pdir, "sobc.md"),
|
||||
"requirements": check_artifact(pdir, "requirements.md"),
|
||||
"data_model": check_artifact(pdir, "data-model.md"),
|
||||
"research_findings": check_artifact(pdir, "research-findings.md"),
|
||||
"wardley_maps": check_artifact(pdir, "wardley-maps/"),
|
||||
"sow": check_artifact(pdir, "sow.md"),
|
||||
"evaluation_criteria": check_artifact(pdir, "evaluation-criteria.md"),
|
||||
"vendors": check_artifact(pdir, "vendors/"),
|
||||
},
|
||||
})
|
||||
|
||||
output = {
|
||||
"repository_root": repo_root,
|
||||
"projects_dir": projects_dir,
|
||||
"project_count": project_count,
|
||||
"projects": projects,
|
||||
}
|
||||
print(json.dumps(output, indent=2))
|
||||
sys.exit(0)
|
||||
|
||||
# Text output mode
|
||||
print("ArcKit Projects")
|
||||
print("===============")
|
||||
print()
|
||||
print(f"Repository: {repo_root}")
|
||||
print(f"Projects found: {project_count}")
|
||||
print()
|
||||
|
||||
for pd in project_dirs:
|
||||
pdir = str(pd)
|
||||
project_name = pd.name
|
||||
project_number = get_project_number_from_dir(pdir) or ""
|
||||
vendor_count = count_vendors(pdir)
|
||||
external_doc_count = count_external_docs(pdir)
|
||||
completion = calculate_completion(pdir)
|
||||
status = get_status_emoji(completion)
|
||||
|
||||
print(f"{status} [{project_number}] {project_name} ({completion}% complete)")
|
||||
|
||||
if args.verbose:
|
||||
print(f" Path: {pdir}")
|
||||
print(" Artifacts:")
|
||||
|
||||
checks = [
|
||||
("stakeholder-drivers.md", "Stakeholder Drivers"),
|
||||
("risk-register.md", "Risk Register"),
|
||||
("sobc.md", "Strategic Outline Business Case"),
|
||||
("requirements.md", "Requirements"),
|
||||
("data-model.md", "Data Model"),
|
||||
("research-findings.md", "Research Findings"),
|
||||
("wardley-maps/", "Wardley Maps"),
|
||||
("sow.md", "Statement of Work"),
|
||||
("evaluation-criteria.md", "Evaluation Criteria"),
|
||||
]
|
||||
|
||||
for artifact, label in checks:
|
||||
mark = "\u2713" if check_artifact(pdir, artifact) else "\u2717"
|
||||
print(f" {mark} {label}")
|
||||
|
||||
if vendor_count > 0:
|
||||
print(f" \u2713 Vendor Proposals ({vendor_count})")
|
||||
else:
|
||||
print(" \u2717 Vendor Proposals")
|
||||
|
||||
if external_doc_count > 0:
|
||||
print(f" \u2713 External Documents ({external_doc_count})")
|
||||
else:
|
||||
print(" \u2717 External Documents")
|
||||
|
||||
print()
|
||||
|
||||
print()
|
||||
print("Legend:")
|
||||
print(" \u2705 Complete (100%)")
|
||||
print(" \U0001f7e2 Mostly complete (75-99%)")
|
||||
print(" \U0001f7e1 In progress (50-74%)")
|
||||
print(" \U0001f7e0 Started (25-49%)")
|
||||
print(" \U0001f534 Not started (0-24%)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user