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
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@ All notable changes to this project are documented in this file.

The format is based on Keep a Changelog and follows Semantic Versioning.

## [0.13.0] - 2026-05-19

Synchronized version bump for all `@stackbilt/*` packages to 0.13.0.

### Added
- **`charter bootstrap --security-sensitive`** — new flag that seeds a `SECURITY.md` vulnerability disclosure template, writes hard-fail drift deny patterns to `.charter/patterns/security-deny.json`, and registers a `doctor` check that warns when no `security*` or `l4*` test file is found. Designed for repos handling auth, tokens, or signatures.
- **Security blocker tracking in `charter drift`** — when `security-deny.json` is present, matching violations are surfaced as `BLOCKER` severity and counted in a new `securityBlockers` field in JSON output. CI mode exits non-zero on any security blocker regardless of the drift score threshold.
- **`doctor` security test coverage check** — activates automatically when `.charter/patterns/security-deny.json` exists; passes once a `security*` or `l4*` test file is detected under `tests/`, `__tests__/`, or any `*.test.*` / `*.spec.*` path.

### Changed
- **Unified build-command deprecation warnings** — `login`, `run`, `architect`, and `scaffold` now share a single `printBuildCommandDeprecationWarning` helper that points users at `@stackbilt/build`. Suppress with `CHARTER_NO_DEPRECATION_WARNING=1` or `--no-deprecation-warning`.

## [0.12.1] - 2026-05-05

Hotfix release for npm consumer install breakage in 0.12.0.

### Fixed
- Published `@stackbilt/*` package manifests now use concrete semver ranges for internal runtime dependencies (for example, `^0.12.1`) instead of `workspace:` protocol specifiers.
- `pnpm add @stackbilt/cli` now succeeds in non-workspace consumer repos.

### Changed
- All publishable `@stackbilt/*` packages are synchronized at `0.12.1`.
- Publishing runbook updated to require concrete publish-time dependency validation and to remove stale `pnpm pack --dry-run` guidance.

### Deprecated
- Broken `0.12.0` npm versions were deprecated for impacted packages with an upgrade message to `0.12.1`.

## [0.12.0] - 2026-04-18

Synchronized version bump for all `@stackbilt/*` packages to 0.12.0.
Expand Down Expand Up @@ -478,6 +505,8 @@ All 345 existing tests pass.
### Security
- Added repository security policy and reporting process.

[0.13.0]: https://github.com/stackbilt-dev/charter/compare/v0.12.1...v0.13.0
[0.12.1]: https://github.com/stackbilt-dev/charter/compare/v0.12.0...v0.12.1
[0.5.0]: https://github.com/stackbilt-dev/charter/compare/v0.4.2...v0.5.0
[0.4.2]: https://github.com/stackbilt-dev/charter/compare/v0.4.1...v0.4.2
[0.4.1]: https://github.com/stackbilt-dev/charter/compare/v0.4.0...v0.4.1
Expand Down
6 changes: 2 additions & 4 deletions PUBLISHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ for pkg in packages/*/package.json; do
done
```

> **Note:** Do NOT manually replace `workspace:^` dependency specifiers.
> PNPM automatically resolves `workspace:^` to concrete version ranges
> (e.g. `"^0.4.0"`) in the published tarball. The source files stay as-is.
> **Note:** Published package `dependencies` must not contain `workspace:`
> protocol specifiers. Use concrete semver ranges (for example, `"^0.4.0"`).

## Phase 3: Artifact Validation (Required)

Expand Down Expand Up @@ -121,7 +120,6 @@ Publish in this order:

```bash
# All packages declare publishConfig.access: "public", so --access flag is not needed.
# PNPM resolves workspace:^ to concrete versions in the published tarball automatically.
pnpm --filter @stackbilt/types publish
pnpm --filter @stackbilt/core publish
pnpm --filter @stackbilt/adf publish
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ npm install --save-dev @stackbilt/cli

For pnpm workspaces: `pnpm add -Dw @stackbilt/cli`. For global install: `npm install -g @stackbilt/cli`.

> If you installed `@stackbilt/cli@0.12.0`, upgrade to `0.12.1` or later. `0.12.0` was deprecated due to invalid published `workspace:` dependency specifiers for external consumers.

> **WSL2 note:** If your project lives on the Windows filesystem (`/mnt/c/...`), pnpm may fail with `EACCES` permission errors due to WSL2/NTFS cross-filesystem limitations with atomic renames. Use `pnpm add --force` to work around this, or move your project to a Linux-native path (e.g., `~/projects/`) for best performance.

## AI agent governance with ADF
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
},
"packageManager": "pnpm@9.15.9",
"devDependencies": {
"@stackbilt/cli": "workspace:^",
"@types/node": "^25.6.0",
"tsx": "^4.21.0",
"@stackbilt/cli": "^0.13.0",
"@types/node": "^25.9.0",
"tsx": "^4.22.2",
"typescript": "~5.8.2",
"vitest": "^4.1.6",
"zod": "^3.24.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/adf/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@stackbilt/adf",
"sideEffects": false,
"version": "0.12.0",
"version": "0.13.0",
"description": "ADF (Attention-Directed Format) — AST-backed context format for AI agents",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/blast/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@stackbilt/blast",
"sideEffects": false,
"version": "0.12.0",
"version": "0.13.0",
"description": "Blast radius analysis via reverse dependency graph + BFS traversal",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
4 changes: 2 additions & 2 deletions packages/ci/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@stackbilt/ci",
"sideEffects": false,
"version": "0.12.0",
"version": "0.13.0",
"description": "GitHub Actions adapter for Charter governance checks",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down Expand Up @@ -29,7 +29,7 @@
},
"homepage": "https://github.com/Stackbilt-dev/charter#readme",
"dependencies": {
"@stackbilt/types": "workspace:^"
"@stackbilt/types": "^0.13.0"
},
"scripts": {
"prepublishOnly": "node ../../scripts/ensure-pnpm-publish.mjs"
Expand Down
4 changes: 2 additions & 2 deletions packages/classify/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@stackbilt/classify",
"sideEffects": false,
"version": "0.12.0",
"version": "0.13.0",
"description": "Heuristic change classification (SURFACE/LOCAL/CROSS_CUTTING)",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down Expand Up @@ -29,7 +29,7 @@
},
"homepage": "https://github.com/Stackbilt-dev/charter#readme",
"dependencies": {
"@stackbilt/types": "workspace:^"
"@stackbilt/types": "^0.13.0"
},
"scripts": {
"prepublishOnly": "node ../../scripts/ensure-pnpm-publish.mjs"
Expand Down
20 changes: 10 additions & 10 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@stackbilt/cli",
"sideEffects": false,
"version": "0.12.0",
"version": "0.13.0",
"description": "Charter CLI — repo-level governance checks + architecture scaffolding",
"bin": {
"charter": "./dist/bin.js",
Expand Down Expand Up @@ -41,15 +41,15 @@
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"@stackbilt/adf": "workspace:^",
"@stackbilt/blast": "workspace:^",
"@stackbilt/classify": "workspace:^",
"@stackbilt/core": "workspace:^",
"@stackbilt/drift": "workspace:^",
"@stackbilt/git": "workspace:^",
"@stackbilt/surface": "workspace:^",
"@stackbilt/types": "workspace:^",
"@stackbilt/validate": "workspace:^"
"@stackbilt/adf": "^0.13.0",
"@stackbilt/blast": "^0.13.0",
"@stackbilt/classify": "^0.13.0",
"@stackbilt/core": "^0.13.0",
"@stackbilt/drift": "^0.13.0",
"@stackbilt/git": "^0.13.0",
"@stackbilt/surface": "^0.13.0",
"@stackbilt/types": "^0.13.0",
"@stackbilt/validate": "^0.13.0"
},
"license": "Apache-2.0",
"author": "Stackbilt LLC"
Expand Down
72 changes: 72 additions & 0 deletions packages/cli/src/__tests__/deprecated-commands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import { architectCommand } from '../commands/architect';
import { runCommand } from '../commands/run';
import { scaffoldCommand } from '../commands/scaffold';
import { loginCommand } from '../commands/login';
import type { CLIOptions } from '../index';
import { CLIError } from '../index';
import { DEPRECATION_WARNING_ENV_VAR } from '../commands/deprecation-warning';

const baseOptions: CLIOptions = {
format: 'text',
configPath: '.charter',
ciMode: false,
yes: false,
};

describe('deprecated build commands warnings', () => {
const originalSuppress = process.env[DEPRECATION_WARNING_ENV_VAR];

beforeEach(() => {
delete process.env[DEPRECATION_WARNING_ENV_VAR];
vi.restoreAllMocks();
});

afterEach(() => {
if (originalSuppress === undefined) {
delete process.env[DEPRECATION_WARNING_ENV_VAR];
} else {
process.env[DEPRECATION_WARNING_ENV_VAR] = originalSuppress;
}
vi.restoreAllMocks();
});

it('emits warning for login', async () => {
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
vi.spyOn(console, 'log').mockImplementation(() => {});

await loginCommand(baseOptions, []);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toContain('charter login');
expect(stderrOutput).toContain('@stackbilt/build');
});

it('emits warning for architect', async () => {
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);

await expect(architectCommand(baseOptions, [])).rejects.toBeInstanceOf(CLIError);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toContain('charter architect');
});

it('emits warning for run', async () => {
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);

await expect(runCommand(baseOptions, [])).rejects.toBeInstanceOf(CLIError);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toContain('charter run');
});

it('emits warning for scaffold', async () => {
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
const options = { ...baseOptions, configPath: '.charter-missing-cache-for-test' };

await expect(scaffoldCommand(options, [])).rejects.toBeInstanceOf(CLIError);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toContain('charter scaffold');
});
});
30 changes: 29 additions & 1 deletion packages/cli/src/__tests__/login.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { loginCommand } from '../commands/login';
import type { CLIOptions } from '../index';
import { API_KEY_ENV_VAR } from '../credentials';
import { DEPRECATION_WARNING_ENV_VAR } from '../commands/deprecation-warning';

const options: CLIOptions = {
format: 'text',
Expand All @@ -12,6 +13,7 @@ const options: CLIOptions = {

describe('charter login — deprecation notice', () => {
const originalEnv = process.env[API_KEY_ENV_VAR];
const originalSuppress = process.env[DEPRECATION_WARNING_ENV_VAR];

beforeEach(() => {
delete process.env[API_KEY_ENV_VAR];
Expand All @@ -23,6 +25,11 @@ describe('charter login — deprecation notice', () => {
} else {
process.env[API_KEY_ENV_VAR] = originalEnv;
}
if (originalSuppress === undefined) {
delete process.env[DEPRECATION_WARNING_ENV_VAR];
} else {
process.env[DEPRECATION_WARNING_ENV_VAR] = originalSuppress;
}
vi.restoreAllMocks();
});

Expand All @@ -34,7 +41,7 @@ describe('charter login — deprecation notice', () => {

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toMatch(/deprecated/i);
expect(stderrOutput).toContain(API_KEY_ENV_VAR);
expect(stderrOutput).toContain('@stackbilt/build');
});

it('reports env-var usage when STACKBILT_API_KEY is set and no --key flag', async () => {
Expand All @@ -47,4 +54,25 @@ describe('charter login — deprecation notice', () => {
const stdoutOutput = log.mock.calls.map((c) => String(c[0])).join('\n');
expect(stdoutOutput).toMatch(new RegExp(`Using ${API_KEY_ENV_VAR} from environment`));
});

it('suppresses warning when CHARTER_NO_DEPRECATION_WARNING=1', async () => {
process.env[DEPRECATION_WARNING_ENV_VAR] = '1';
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
vi.spyOn(console, 'log').mockImplementation(() => {});

await loginCommand(options, []);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toBe('');
});

it('suppresses warning when --no-deprecation-warning is passed', async () => {
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
vi.spyOn(console, 'log').mockImplementation(() => {});

await loginCommand(options, ['--no-deprecation-warning']);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toBe('');
});
});
3 changes: 3 additions & 0 deletions packages/cli/src/commands/architect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ import { EXIT_CODE, CLIError } from '../index';
import { getFlag } from '../flags';
import { resolveApiKey, API_KEY_ENV_VAR } from '../credentials';
import { EngineClient, type BuildRequest, type BuildResult } from '../http-client';
import { printBuildCommandDeprecationWarning } from './deprecation-warning';

export async function architectCommand(options: CLIOptions, args: string[]): Promise<number> {
printBuildCommandDeprecationWarning('architect', args);

// Parse description from positional arg or --file
const filePath = getFlag(args, '--file');
const positional = args.filter(a => !a.startsWith('-') && a !== filePath);
Expand Down
20 changes: 20 additions & 0 deletions packages/cli/src/commands/deprecation-warning.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const RFC_112_URL = 'https://github.com/Stackbilt-dev/charter/issues/112';
export const DEPRECATION_WARNING_ENV_VAR = 'CHARTER_NO_DEPRECATION_WARNING';
export const DEPRECATION_WARNING_FLAG = '--no-deprecation-warning';

function warningSuppressed(args: string[]): boolean {
return args.includes(DEPRECATION_WARNING_FLAG) || process.env[DEPRECATION_WARNING_ENV_VAR] === '1';
}

export function printBuildCommandDeprecationWarning(command: string, args: string[]): void {
if (warningSuppressed(args)) {
return;
}

process.stderr.write(
`⚠ charter ${command} is deprecated and will be removed in Charter 1.0.\n` +
' Install @stackbilt/build for the long-term home of this command:\n' +
' npm install -g @stackbilt/build\n' +
` See ${RFC_112_URL} for context.\n`,
);
}
6 changes: 2 additions & 4 deletions packages/cli/src/commands/drift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ function loadSecurityDenyPatterns(configPath: string): Pattern[] {
documentationUrl: null,
relatedLedgerId: null,
status: 'ACTIVE' as const,
createdAt: new Date().toISOString(),
createdAt: typeof item.createdAt === 'string' ? item.createdAt : '1970-01-01T00:00:00.000Z',
projectId: null,
}));
} catch {
Expand All @@ -177,12 +177,10 @@ function loadSecurityDenyPatterns(configPath: string): Pattern[] {
}

function mergeReports(base: DriftReport, securityViolations: DriftViolation[], extraPatternCount: number): DriftReport {
const violations = [...base.violations, ...securityViolations];
return {
...base,
violations,
violations: [...base.violations, ...securityViolations],
scannedPatterns: base.scannedPatterns + extraPatternCount,
score: Math.max(0, 1.0 - (violations.length * 0.1)),
};
}

Expand Down
Loading
Loading