Spec-driven development (SDD) for AI coding assistants.
This report summarizes scanner evidence for Fission-AI/OpenSpec, a TypeScript project with 73,498 GitHub stars. SynthScan examined 236,713 lines of code across 1277 source files, recording 267 pattern matches distributed across 8 syntactic categories. The legacy adjusted pattern signal is 1.5 (Low pattern signal); it is not an authorship estimate.
Use the findings to ask whether maintainers can explain intent, verify dependencies, simplify unnecessary scaffolding, and focus review effort. Risk Payload v1 adds ownership, supply-chain, over-engineering, and review-effort vectors when available. These are prompts for human verification, not probabilities, security findings, or measured maintenance costs.
Use each vector to guide manual verification of the underlying findings.
Payload schema: 1.0.0 · Engine: 2.0.0 · Scoring: heuristic-density-v1 · Scan date: 2026-10-31.
Can maintainers explain intent, assumptions, and trade-offs? 249 finding(s).
Are imports declared and package/API names verified? 0 finding(s).
Are wrappers, guards, and repeated structures necessary? 213 finding(s).
Which changes need focused human review? 267 finding(s).
This chart tracks the legacy adjusted pattern score across scan runs. Changes can reflect source changes, scan coverage, or detector/scoring versions; they do not measure AI adoption or prove that a team refactored code. Compare runs only when their scoring versions are compatible. The dashed line tracks the raw pattern-hit count.
Groups findings by configured severity weight. Severity is not a confidence estimate, security rating, or proof that a package is missing. Verify imports against the project's manifests and inspect each finding in its local design context.
This horizontal bar chart shows where configured pattern scores accumulate across directories. Treat the bars as locations to inspect, not as evidence of authorship or code quality. Use the file-level findings to decide whether a directory needs a focused ownership, dependency, complexity, or review check.
The scanner identified 267 distinct pattern matches across 8 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a detector match to inspect in context. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the configured finding weight (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — legacy comment-context matches receive a ×1.5 score weight. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier under the legacy scoring model.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | test/core/archive.test.ts | 8655 | async function createChangeWithDeltaSpec(changeName: string): Promise<string> { | CODE |
| LOW | test/core/github-copilot-cloud-agent.test.ts | 108 | async function linkDirectoryOutsideProject(outsideDir: string): Promise<void> { | CODE |
| LOW | test/core/update-dry-run.test.ts | 109 | async function expectPlanMatchesRealUpdate( | CODE |
| LOW | test/core/update-dry-run.test.ts | 120 | async function expectFullPlanMatchesRealUpdate(options: { force?: boolean } = {}) { | CODE |
| LOW | test/core/completions/command-registry.test.ts | 75 | function toCommanderPositionalShapes(command: Command): PositionalShape[] { | CODE |
| LOW | test/core/templates/update-change.test.ts | 113 | function writeAuthorizationsOutsideStepFive(body: string, label: string): string[] { | CODE |
| LOW | test/core/templates/profile-handoffs.test.ts | 29 | function expectInstalledReferences(content: string, workflows: readonly string[], label: string): void { | CODE |
| LOW | test/cli-e2e/store-lifecycle.test.ts | 107 | async function writeCompletedChangeArtifacts( | CODE |
| LOW | test/commands/config-profile.test.ts | 85 | function setupDriftedProjectArtifacts(projectDir: string): void { | CODE |
| LOW | test/commands/config-profile.test.ts | 92 | function setupSyncedCoreBothArtifacts(projectDir: string): void { | CODE |
| LOW | test/commands/config-profile.test.ts | 116 | function addExtraVerifyWorkflowArtifacts(projectDir: string): void { | CODE |
| LOW | test/commands/store.test.ts | 99 | function expectHealthyOpenSpecRoot(root: string): void { | CODE |
| LOW | test/commands/store.test.ts | 106 | function expectNoGeneratedAgentOrBetaArtifacts(root: string): void { | CODE |
| LOW | test/commands/legacy-groups-removed.test.ts | 58 | function writeWorkspaceViewFixture(dir: string): void { | CODE |
| LOW | test/helpers/store-git.ts | 10 | export function createHealthyOpenSpecRoot(root: string, configName = 'config.yaml'): void { | CODE |
| LOW | test/helpers/run-cli.ts | 115 | export function terminateActiveCliChildren(): void { | CODE |
| LOW | …/changes/archive/2026-02-17-add-verify-skill/design.md | 30 | export function getVerifyChangeSkillTemplate(): SkillTemplate | CODE |
| LOW | …/changes/archive/2026-02-17-add-verify-skill/design.md | 31 | export function getOpsxVerifyCommandTemplate(): CommandTemplate | CODE |
| LOW | src/core/legacy-cleanup.ts | 187 | function getManagedGlobalLegacyPromptMetadata(filePath: string): LegacyGlobalPromptMatch | undefined { | CODE |
| LOW | src/core/legacy-cleanup.ts | 335 | export async function detectLegacySlashCommands( | CODE |
| LOW | src/core/legacy-cleanup.ts | 491 | async function detectLegacyGlobalPromptFiles(): Promise<LegacyGlobalPromptMatch[]> { | CODE |
| LOW | src/core/legacy-cleanup.ts | 523 | async function findLegacySlashCommandFiles( | CODE |
| LOW | src/core/legacy-cleanup.ts | 568 | export async function detectLegacyStructureFiles( | CODE |
| LOW | src/core/legacy-cleanup.ts | 998 | export function formatDeferredGlobalPromptSummary(detection: LegacyDetectionResult): string { | CODE |
| LOW | src/core/legacy-cleanup.ts | 1022 | export function getToolsFromLegacyArtifacts(detection: LegacyDetectionResult): string[] { | CODE |
| LOW | src/core/legacy-cleanup.ts | 1051 | function legacyManagedFileNamesForDir(dir: string): readonly string[] { | CODE |
| LOW | src/core/legacy-cleanup.ts | 1082 | export function getLegacyGlobalPromptMatches(detection: LegacyDetectionResult): LegacyGlobalPromptMatch[] { | CODE |
| LOW | src/core/legacy-cleanup.ts | 1096 | export function getLegacyWorkflowIdsForTool( | CODE |
| LOW | src/core/legacy-cleanup.ts | 1131 | export function omitGlobalLegacyPromptFiles(detection: LegacyDetectionResult): LegacyDetectionResult { | CODE |
| LOW | src/core/legacy-cleanup.ts | 1173 | export function pickGlobalLegacyPromptFiles( | CODE |
| LOW | src/core/legacy-cleanup.ts | 1201 | export function formatProjectMdMigrationHint(): string { | CODE |
| LOW | src/core/command-surface.ts | 17 | export function resolveCommandSurfaceCapability(toolId: string): CommandSurfaceCapability { | CODE |
| LOW⚡ | src/core/command-surface.ts | 29 | export function shouldGenerateSkillsForTool(toolId: string, delivery: Delivery): boolean { | CODE |
| LOW⚡ | src/core/command-surface.ts | 33 | export function shouldRemoveSkillsForTool(toolId: string, delivery: Delivery): boolean { | CODE |
| LOW⚡ | src/core/command-surface.ts | 37 | export function shouldGenerateCommandsForTool(toolId: string, delivery: Delivery): boolean { | CODE |
| LOW⚡ | src/core/command-surface.ts | 41 | export function shouldReconcileCommandFilesForTool(toolId: string, delivery: Delivery): boolean { | CODE |
| LOW | src/core/migration.ts | 200 | function collectLegacyToolMigrations( | CODE |
| LOW | src/core/migration.ts | 521 | function scanInstalledWorkflowArtifacts( | CODE |
| LOW | src/core/root-selection.ts | 302 | async function resolveNearestOrDeclaredRoot( | CODE |
| LOW | src/core/root-selection.ts | 400 | async function suggestStoreFromGitRemote( | CODE |
| LOW | src/core/archive.ts | 98 | function describeUnaccountedContent(lines: string[]): string { | CODE |
| LOW | src/core/archive.ts | 407 | async function fingerprintDirectoryContents(root: string): Promise<string> { | CODE |
| LOW | src/core/archive.ts | 474 | async function assertCopiedDirectoryUnchanged( | CODE |
| LOW | src/core/archive.ts | 514 | async function listTreeEntriesDeepestFirst( | CODE |
| LOW | src/core/archive.ts | 756 | async function assertArchiveDestinationAvailable( | CODE |
| LOW | src/core/archive.ts | 980 | async function fingerprintPortableContent(filePath: string): Promise<string> { | CODE |
| LOW | src/core/archive.ts | 999 | async function assertRetirementAuthorization( | CODE |
| LOW | src/core/archive.ts | 1038 | async function assertDistinctMutationTargets(mutations: SpecMutation[]): Promise<void> { | CODE |
| LOW | src/core/archive.ts | 1265 | async function finalizeRetirementBackups( | CODE |
| LOW | src/core/planning-home.ts | 74 | export function resolveCurrentPlanningHomeSync( | CODE |
| LOW | src/core/onboarding-commands.ts | 61 | export function formatOptionalWorkflowsNote( | CODE |
| LOW | src/core/shared-skill-target.ts | 103 | export function resolveSharedSkillWriters( | CODE |
| LOW | src/core/shared-skill-target.ts | 153 | export function reconcileSharedSkillTargets( | CODE |
| LOW | src/core/shared-skill-target.ts | 223 | export function isSharedSkillTargetActive( | CODE |
| LOW | src/core/shared-skill-target.ts | 264 | export function sharedSkillRootOwnedByOther(projectPath: string, toolId: string): boolean { | CODE |
| LOW | src/core/shared-skill-target.ts | 273 | export function sharedSkillTargetMarkerPath(projectPath: string, toolId: string): string | null { | CODE |
| LOW | src/core/profile-sync-drift.ts | 54 | export function getConfiguredToolsForProfileSync( | CODE |
| LOW | src/core/profile-sync-drift.ts | 69 | export function hasToolProfileOrDeliveryDrift( | CODE |
| LOW | src/core/profile-sync-drift.ts | 183 | export function getToolsNeedingProfileSync( | CODE |
| LOW | src/core/profile-sync-drift.ts | 198 | function getInstalledWorkflowsForTool( | CODE |
| 135 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | src/core/init.ts | 380 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 382 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 492 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 494 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 632 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 634 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 895 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 897 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 938 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 940 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 1086 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 1088 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 1178 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/init.ts | 1180 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/core/github-copilot/cloud-agent.ts | 508 | // ───────────────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/core/github-copilot/cloud-agent.ts | 516 | // ───────────────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | src/cli/index.ts | 778 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| MEDIUM | src/cli/index.ts | 780 | // ═══════════════════════════════════════════════════════════ | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | CHANGELOG.md | 103 | - [#2083](https://github.com/Fission-AI/OpenSpec/pull/2083) [`3fea2a9`](https://github.com/Fission-AI/OpenSpec/commit/3f | CODE |
| MEDIUM | CHANGELOG.md | 103 | - [#2083](https://github.com/Fission-AI/OpenSpec/pull/2083) [`3fea2a9`](https://github.com/Fission-AI/OpenSpec/commit/3f | CODE |
| MEDIUM | CHANGELOG.md | 354 | - [#1840](https://github.com/Fission-AI/OpenSpec/pull/1840) [`fede536`](https://github.com/Fission-AI/OpenSpec/commit/fe | CODE |
| MEDIUM | CHANGELOG.md | 354 | - [#1840](https://github.com/Fission-AI/OpenSpec/pull/1840) [`fede536`](https://github.com/Fission-AI/OpenSpec/commit/fe | CODE |
| MEDIUM | CHANGELOG.md | 430 | - [#1716](https://github.com/Fission-AI/OpenSpec/pull/1716) [`7010e26`](https://github.com/Fission-AI/OpenSpec/commit/70 | CODE |
| MEDIUM | CHANGELOG.md | 430 | - [#1716](https://github.com/Fission-AI/OpenSpec/pull/1716) [`7010e26`](https://github.com/Fission-AI/OpenSpec/commit/70 | CODE |
| MEDIUM | test/explore-docs-claims.test.ts | 11 | // made it scaffold with `openspec new change` first. What stays true is narrower: explore never writes code, | COMMENT |
| MEDIUM | test/core/templates/update-change.test.ts | 141 | // the guardrails said to write only after the user confirms. "Apply" is a write | COMMENT |
| MEDIUM⚡ | test/core/templates/explore.test.ts | 741 | // Regression for #869: the seamless capture path let explore scaffold a | COMMENT |
| MEDIUM⚡ | test/core/templates/explore.test.ts | 741 | // Regression for #869: the seamless capture path let explore scaffold a | COMMENT |
| MEDIUM | test/commands/store-root-selection.test.ts | 812 | // scaffold a change into an unrelated repository. | COMMENT |
| MEDIUM | test/commands/declared-store-fallback.test.ts | 242 | // A broken declaration must not be buried under a scaffold. | COMMENT |
| MEDIUM | docs-lab/guides/apply.md | 9 | ## Managing the context window | COMMENT |
| MEDIUM | docs-lab/reference/skills.md | 57 | | [openspec-new-change](#openspec-new-change) | Start a change proposal as an empty scaffold | Optional | | CODE |
| MEDIUM | docs-lab/customize/profiles.md | 28 | | [`new`](../reference/skills.md#openspec-new-change) | Start a change proposal as an empty scaffold | | CODE |
| MEDIUM | docs/opsx.md | 190 | /opsx:new # scaffold only | CODE |
| MEDIUM | …plify-context-and-workspace-model/workset-direction.md | 76 | ## 5. Grammar principles (record as standing guardrails) | COMMENT |
| MEDIUM | src/utils/change-utils.ts | 175 | // Creating a change may scaffold or complete the root itself (an | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | pnpm-workspace.yaml | 1 | packages: | COMMENT |
| LOW | pnpm-workspace.yaml | 21 | # (check: pnpm why js-yaml). | COMMENT |
| LOW | test/explore-docs-claims.test.ts | 1 | import { describe, expect, it } from 'vitest'; | COMMENT |
| LOW | test/core/specs-apply.salvage.test.ts | 1 | import { describe, it, expect, beforeEach, afterEach } from 'vitest'; | COMMENT |
| LOW | scripts/binary/install.sh | 1 | #!/bin/sh | COMMENT |
| LOW | .github/dependabot.yml | 1 | version: 2 | COMMENT |
| LOW | .github/workflows/release-prepare.yml | 61 | # Preserve the v1 release path: pushes use the GitHub App token from | COMMENT |
| LOW | .github/workflows/security.yml | 81 | # manual override. | COMMENT |
| LOW | src/core/migration.ts | 41 | /** | COMMENT |
| LOW | src/core/archive.ts | 141 | // ordinary write, which fails validation exactly as it always did - and the | COMMENT |
| LOW | src/core/archive.ts | 1461 | // Validate proposal.md (informative only; human mode prints warnings) | COMMENT |
| LOW | src/core/archive.ts | 1501 | let hasDeltaSpecs = rootSpecStat?.isFile() === true; | COMMENT |
| LOW | src/core/archive.ts | 1881 | // This run is what emptied the capability, and "no | COMMENT |
| LOW | src/core/archive.ts | 2101 | wroteAny = true; | COMMENT |
| LOW | src/core/archive.ts | 2141 | // A command is offered only when pasting it where archive was run | COMMENT |
| LOW | src/core/specs-apply.ts | 861 | let bulletsSeen = false; | COMMENT |
| LOW | src/core/specs-apply.ts | 881 | paragraphOpen = false; | COMMENT |
| LOW | src/core/specs-apply.ts | 901 | const lazilyContinuesBullet = | COMMENT |
| LOW | src/core/specs-apply.ts | 1381 | */ | COMMENT |
| LOW | src/core/change-metadata/schema.ts | 41 | .string() | COMMENT |
| LOW | src/core/shared/tool-detection.ts | 281 | ): string | null { | COMMENT |
| LOW | src/core/github-copilot/cloud-agent.ts | 581 | return; | COMMENT |
| LOW | src/cli/commands/store.ts | 101 | .join(', '); | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM⚡ | test/core/templates/explore.test.ts | 741 | // Regression for #869: the seamless capture path let explore scaffold a | COMMENT |
| MEDIUM⚡ | test/core/templates/explore.test.ts | 741 | // Regression for #869: the seamless capture path let explore scaffold a | COMMENT |
| MEDIUM | src/commands/workflow/templates.ts | 59 | // Using path.relative is more robust than startsWith for path comparisons | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | README_OLD.md | 141 | #### Step 1: Install the CLI globally | COMMENT |
| LOW | README_OLD.md | 187 | #### Step 2: Initialize OpenSpec in your project | COMMENT |
| LOW | docs-lab/start/quickstart.md | 32 | ## Step 1: Explore | COMMENT |
| LOW | docs-lab/start/quickstart.md | 50 | ## Step 2: Propose | COMMENT |
| LOW | docs-lab/start/quickstart.md | 70 | ## Step 3: Review and correct the plan | COMMENT |
| LOW | docs-lab/start/quickstart.md | 83 | ## Step 4: Apply | COMMENT |
| LOW | docs-lab/start/quickstart.md | 97 | ## Step 5: Archive | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/update-flake.sh | 51 | # Check if pnpm-lock.yaml exists | COMMENT |
| LOW | scripts/update-flake.sh | 101 | # Check if hash changed | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | src/core/templates/workflows/onboard.ts | 245 | ## Creating a Change | COMMENT |