Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ C:*
node_modules/
.pnpm-store/
__pycache__/
# cc-taskrunner worktree protection
C:*
node_modules/
.pnpm-store/
__pycache__/
27 changes: 17 additions & 10 deletions packages/cli/src/commands/adf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,9 @@ function adfInit(options: CLIOptions, args: string[]): number {
if (options.format === 'json') {
console.log(JSON.stringify({ ...result, nextActions: ['charter adf fmt .ai/core.adf --check', 'charter adf bundle --task "<prompt>"'] }, null, 2));
} else {
console.log(` .ai/ already exists at ${aiDir}/`);
console.log('');
console.log(' .ai/ directory already exists. Run \'charter doctor\' to check for issues.');
console.log('');
console.log(' Use --force (or --yes) to overwrite.');
console.log(' To add a single module: charter adf init --module <name>');
}
Expand Down Expand Up @@ -403,19 +405,24 @@ function adfInit(options: CLIOptions, args: string[]): number {
],
}, null, 2));
} else {
console.log(` Initialized ADF context at ${aiDir}/`);
console.log('');
console.log(' Created:');
for (const file of result.files) {
console.log(` ${file}`);
console.log(' \u2713 Created .ai/ directory');
console.log('');
console.log(' Your AI governance is ready. Here\'s what was created:');
console.log('');
console.log(' .ai/manifest.adf \u2014 Module router (controls what loads when)');
console.log(' .ai/core.adf \u2014 Universal rules (always loaded)');
console.log(' .ai/state.adf \u2014 Project state tracking');
for (const file of moduleFiles) {
console.log(` .ai/${file.padEnd(19)}\u2014 Domain-specific rules (on-demand)`);
}
console.log('');
console.log(' Next steps:');
console.log(' 1. Run: charter adf populate # auto-fill ADF files from codebase signals');
console.log(' 2. Edit core.adf to add project-specific constraints and rules');
console.log(' 3. Run: charter adf fmt .ai/core.adf --check');
console.log(' 4. Run: charter adf bundle --task "<your task>" to compile context for an agent session');
console.log(' (The verify:adf script runs this automatically in CI)');
console.log(' 1. Edit .ai/core.adf to add your project\'s constraints');
console.log(' 2. Run \'charter doctor\' to validate your setup');
console.log(' 3. Run \'charter adf bundle --task "<prompt>"\' to see the assembled context');
console.log('');
console.log(' Docs: https://github.com/Stackbilt-dev/charter');
}

return EXIT_CODE.SUCCESS;
Expand Down
160 changes: 152 additions & 8 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import * as fs from 'node:fs';
import * as path from 'node:path';
import * as readline from 'node:readline';
import type { CLIOptions } from '../index';
import { EXIT_CODE } from '../index';
import { getFlag } from '../flags';
Expand Down Expand Up @@ -247,8 +248,15 @@ interface InitializeOptions {

export async function initCommand(options: CLIOptions, args: string[] = []): Promise<number> {
const force = options.yes || args.includes('--force');
const guided = args.includes('--guided');
const presetFlag = getFlag(args, '--preset');
const preset = isValidPreset(presetFlag) ? presetFlag : undefined;

// --guided: interactive mode that asks questions before scaffolding
if (guided) {
return guidedInit(options, force);
}

const result = initializeCharter(options.configPath, force, { preset });

if (options.format === 'json') {
Expand All @@ -257,22 +265,31 @@ export async function initCommand(options: CLIOptions, args: string[] = []): Pro
}

if (!result.created) {
console.log(` .charter/ already exists at ${result.configPath}`);
console.log('');
console.log(' .charter/ directory already exists. Run \'charter doctor\' to check for issues.');
console.log('');
console.log(' Use --config <path> for a different location, or --force to overwrite templates.');
return EXIT_CODE.SUCCESS;
}

console.log(` Initialized .charter/ at ${result.configPath}/`);
console.log('');
console.log(' Created:');
for (const file of result.files) {
console.log(` ${file}`);
}
console.log(' \u2713 Created .charter/ directory');
console.log('');
console.log(' Your governance config is ready. Here\'s what was created:');
console.log('');
console.log(' config.json \u2014 Project settings and thresholds');
console.log(' patterns/blessed-stack.json \u2014 Technology stack patterns (' + (preset || 'fullstack') + ' preset)');
console.log(' policies/governance.md \u2014 Commit governance and change classification');
console.log('');
console.log(' Next steps:');
console.log(' 1. Edit config.json with your project name and thresholds');
console.log(' 2. Define your blessed stack in patterns/*.json');
console.log(' 3. Run: charter validate');
console.log(' 2. Customize patterns/blessed-stack.json for your stack');
console.log(' 3. Run \'charter doctor\' to validate your setup');
console.log(' 4. Run \'charter validate\' to check commit governance');
console.log('');
console.log(' Tip: Run \'charter adf init\' to also scaffold .ai/ context modules.');
console.log('');
console.log(' Docs: https://github.com/Stackbilt-dev/charter');

return EXIT_CODE.SUCCESS;
}
Expand Down Expand Up @@ -331,6 +348,133 @@ function isValidPreset(value: string | undefined): value is StackPreset {
return value === 'worker' || value === 'frontend' || value === 'backend' || value === 'fullstack' || value === 'docs';
}

// ============================================================================
// --guided interactive init
// ============================================================================

function askQuestion(rl: readline.Interface, question: string): Promise<string> {
return new Promise((resolve) => {
rl.question(question, (answer) => resolve(answer.trim()));
});
}

const LANGUAGE_PRESET_MAP: Record<string, StackPreset> = {
typescript: 'fullstack',
python: 'backend',
go: 'backend',
rust: 'backend',
java: 'backend',
react: 'frontend',
vue: 'frontend',
svelte: 'frontend',
};

async function guidedInit(options: CLIOptions, force: boolean): Promise<number> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

try {
console.log('');
console.log(' Charter guided setup');
console.log(' --------------------');
console.log('');

// Q1: Primary language
const langAnswer = await askQuestion(
rl,
' What\'s your primary language/framework? (TypeScript, Python, Go, React, etc.) '
);
const langKey = langAnswer.toLowerCase().replace(/\s+/g, '');
const detectedPreset = LANGUAGE_PRESET_MAP[langKey];

// Q2: Cloudflare Workers
const cfAnswer = await askQuestion(
rl,
' Do you use Cloudflare Workers? (y/N) '
);
const useCloudflare = cfAnswer.toLowerCase() === 'y' || cfAnswer.toLowerCase() === 'yes';

// Q3: Auth governance
const authAnswer = await askQuestion(
rl,
' Do you want auth governance patterns? (y/N) '
);
const useAuth = authAnswer.toLowerCase() === 'y' || authAnswer.toLowerCase() === 'yes';

rl.close();

// Determine preset
let preset: StackPreset;
if (useCloudflare) {
preset = 'worker';
} else if (detectedPreset) {
preset = detectedPreset;
} else {
preset = 'fullstack';
}

const initOpts: InitializeOptions = {
preset,
features: {
cloudflare: useCloudflare,
},
};

const result = initializeCharter(options.configPath, force, initOpts);

if (options.format === 'json') {
console.log(JSON.stringify({ ...result, guided: true, preset, useCloudflare, useAuth }, null, 2));
return EXIT_CODE.SUCCESS;
}

if (!result.created) {
console.log('');
console.log(' .charter/ directory already exists. Run \'charter doctor\' to check for issues.');
return EXIT_CODE.SUCCESS;
}

// If auth governance was requested, add auth-specific patterns to governance.md
if (useAuth) {
const policyPath = path.join(options.configPath, 'policies', 'governance.md');
if (fs.existsSync(policyPath)) {
const existing = fs.readFileSync(policyPath, 'utf-8');
const authPolicy = `
## Auth Governance

Authentication and authorization changes are classified as CROSS_CUTTING.
All auth changes require:
- Explicit review from a security-aware reviewer
- Documented threat model consideration
- Session/token lifecycle validation
`;
fs.writeFileSync(policyPath, existing + authPolicy);
}
}

console.log('');
console.log(' \u2713 Created .charter/ directory');
console.log('');
console.log(' Configuration:');
console.log(` Preset: ${preset}${useCloudflare ? ' (with Cloudflare Workers)' : ''}`);
if (useAuth) {
console.log(' Auth governance: enabled');
}
console.log('');
console.log(' Next steps:');
console.log(' 1. Review config.json and patterns/blessed-stack.json');
console.log(' 2. Run \'charter doctor\' to validate your setup');
console.log(' 3. Run \'charter validate\' to check commit governance');
console.log('');
console.log(' Docs: https://github.com/Stackbilt-dev/charter');

return EXIT_CODE.SUCCESS;
} finally {
rl.close();
}
}

function buildPatternTemplate(
preset: StackPreset,
features: InitializeOptions['features']
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ Usage:
One-command repo onboarding (detect + setup + ADF + install + doctor)
charter setup [--ci github] [--preset <worker|frontend|backend|fullstack>] [--detect-only] [--no-dependency-sync]
Bootstrap .charter/ and optional CI workflow
charter init [--preset <worker|frontend|backend|fullstack>]
charter init [--preset <worker|frontend|backend|fullstack>] [--guided]
Scaffold .charter/ config directory
--guided: interactive mode that asks about your stack
charter validate [--range <revset>]
Validate git commits for governance trailers
charter audit [--range <revset>]
Expand Down
Loading