vigiles 2.0.0
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.
- package/.claude/settings.json +46 -0
- package/.claude/settings.local.json +8 -0
- package/.claude-plugin/hooks/post-edit.sh +34 -0
- package/.claude-plugin/hooks/pre-edit.sh +40 -0
- package/.claude-plugin/hooks/session-start.sh +38 -0
- package/.claude-plugin/marketplace.json +14 -0
- package/.claude-plugin/plugin.json +47 -0
- package/.github/workflows/ci.yml +81 -0
- package/.prettierignore +1 -0
- package/.vigiles/generated.d.ts +205 -0
- package/CLAUDE.md +95 -0
- package/CLAUDE.md.spec.ts +142 -0
- package/CONTRIBUTING.md +121 -0
- package/LICENSE +21 -0
- package/README.md +377 -0
- package/action.yml +25 -0
- package/dist/action.d.ts +7 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +180 -0
- package/dist/action.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1267 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.d.ts +2 -0
- package/dist/cli.test.d.ts.map +1 -0
- package/dist/cli.test.js +650 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/compile.d.ts +101 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +503 -0
- package/dist/compile.js.map +1 -0
- package/dist/evolve.d.ts +132 -0
- package/dist/evolve.d.ts.map +1 -0
- package/dist/evolve.js +599 -0
- package/dist/evolve.js.map +1 -0
- package/dist/freshness.d.ts +67 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/freshness.js +244 -0
- package/dist/freshness.js.map +1 -0
- package/dist/freshness.test.d.ts +2 -0
- package/dist/freshness.test.d.ts.map +1 -0
- package/dist/freshness.test.js +356 -0
- package/dist/freshness.test.js.map +1 -0
- package/dist/generate-types.d.ts +34 -0
- package/dist/generate-types.d.ts.map +1 -0
- package/dist/generate-types.js +381 -0
- package/dist/generate-types.js.map +1 -0
- package/dist/inline.d.ts +58 -0
- package/dist/inline.d.ts.map +1 -0
- package/dist/inline.js +142 -0
- package/dist/inline.js.map +1 -0
- package/dist/inline.test.d.ts +5 -0
- package/dist/inline.test.d.ts.map +1 -0
- package/dist/inline.test.js +152 -0
- package/dist/inline.test.js.map +1 -0
- package/dist/linters.d.ts +38 -0
- package/dist/linters.d.ts.map +1 -0
- package/dist/linters.js +588 -0
- package/dist/linters.js.map +1 -0
- package/dist/proofs.d.ts +272 -0
- package/dist/proofs.d.ts.map +1 -0
- package/dist/proofs.js +622 -0
- package/dist/proofs.js.map +1 -0
- package/dist/proofs.test.d.ts +9 -0
- package/dist/proofs.test.d.ts.map +1 -0
- package/dist/proofs.test.js +952 -0
- package/dist/proofs.test.js.map +1 -0
- package/dist/spec.d.ts +258 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +113 -0
- package/dist/spec.js.map +1 -0
- package/dist/spec.test.d.ts +2 -0
- package/dist/spec.test.d.ts.map +1 -0
- package/dist/spec.test.js +1222 -0
- package/dist/spec.test.js.map +1 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +10 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +286 -0
- package/dist/validate.js.map +1 -0
- package/dist/validate.test.d.ts +2 -0
- package/dist/validate.test.d.ts.map +1 -0
- package/dist/validate.test.js +531 -0
- package/dist/validate.test.js.map +1 -0
- package/docs/agent-setup.md +85 -0
- package/docs/agent-workflows.md +103 -0
- package/docs/comparison.md +71 -0
- package/docs/freshness.md +124 -0
- package/docs/inline-mode.md +119 -0
- package/docs/linter-support.md +166 -0
- package/docs/spec-format.md +194 -0
- package/eslint.config.mjs +79 -0
- package/examples/CLAUDE.md +54 -0
- package/examples/CLAUDE.md.spec.ts +65 -0
- package/examples/SKILL.md +50 -0
- package/examples/SKILL.md.spec.ts +57 -0
- package/fixtures/example-project/CLAUDE.md +11 -0
- package/fixtures/example-project/package.json +9 -0
- package/fixtures/example-project/src/index.ts +3 -0
- package/fixtures/example-project/src/utils.test.ts +2 -0
- package/fixtures/example-project/src/utils.ts +3 -0
- package/logo.png +0 -0
- package/package.json +42 -0
- package/research/adoption-strategy.md +111 -0
- package/research/agent-integration.md +145 -0
- package/research/ai-code-quality.md +197 -0
- package/research/code-search-for-agents.md +313 -0
- package/research/competitive-landscape.md +163 -0
- package/research/doc-freshness.md +516 -0
- package/research/executable-specs.md +368 -0
- package/research/feature-ideas.md +464 -0
- package/research/formal-proofs-for-agents.md +338 -0
- package/research/fp-for-agent-harness.md +150 -0
- package/research/fp-for-deterministic-ai.md +131 -0
- package/research/self-evolving-specs.md +298 -0
- package/schemas/claude-md-strict.yml +18 -0
- package/schemas/claude-md.yml +6 -0
- package/schemas/skill-strict.yml +12 -0
- package/schemas/skill.yml +5 -0
- package/skills/audit-feedback-loop/SKILL.md +76 -0
- package/skills/edit-spec/SKILL.md +131 -0
- package/skills/enforce-rules-format/SKILL.md +71 -0
- package/skills/generate-logo/SKILL.md +102 -0
- package/skills/generate-rule/SKILL.md +90 -0
- package/skills/linter-docs/clippy.md +241 -0
- package/skills/linter-docs/eslint.md +384 -0
- package/skills/linter-docs/pylint.md +288 -0
- package/skills/linter-docs/rubocop.md +277 -0
- package/skills/linter-docs/ruff.md +187 -0
- package/skills/linter-docs/stylelint.md +247 -0
- package/skills/migrate-to-spec/SKILL.md +124 -0
- package/skills/pr-to-lint-rule/SKILL.md +97 -0
- package/skills/strengthen/SKILL.md +168 -0
- package/src/action.ts +214 -0
- package/src/cli.test.ts +914 -0
- package/src/cli.ts +1631 -0
- package/src/compile.ts +691 -0
- package/src/evolve.ts +781 -0
- package/src/freshness.test.ts +449 -0
- package/src/freshness.ts +299 -0
- package/src/generate-types.ts +448 -0
- package/src/inline.test.ts +206 -0
- package/src/inline.ts +164 -0
- package/src/linters.ts +739 -0
- package/src/proofs.test.ts +1314 -0
- package/src/proofs.ts +849 -0
- package/src/spec.test.ts +1471 -0
- package/src/spec.ts +427 -0
- package/src/types.ts +117 -0
- package/src/validate.test.ts +701 -0
- package/src/validate.ts +381 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Spec Format Reference
|
|
2
|
+
|
|
3
|
+
vigiles specs are TypeScript files (`*.spec.ts`) that compile to markdown instruction files. The spec is the source of truth; the markdown is a build artifact.
|
|
4
|
+
|
|
5
|
+
## CLAUDE.md Specs
|
|
6
|
+
|
|
7
|
+
Use `claude()` to define a CLAUDE.md spec. Export it as the default export.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { claude, enforce, guidance, file, cmd, ref, instructions } from "vigiles";
|
|
11
|
+
|
|
12
|
+
export default claude({
|
|
13
|
+
target: "CLAUDE.md", // or "AGENTS.md", or ["CLAUDE.md", "AGENTS.md"]
|
|
14
|
+
sections: { ... },
|
|
15
|
+
keyFiles: { ... },
|
|
16
|
+
commands: { ... },
|
|
17
|
+
rules: { ... },
|
|
18
|
+
maxSectionLines: 30, // optional: cap per-section line count
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### `target`
|
|
23
|
+
|
|
24
|
+
`string | string[]` -- Output filename(s). Defaults to `"CLAUDE.md"`. Also used as the `# Heading` in compiled output. Pass an array to compile one spec to multiple targets:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
target: ["CLAUDE.md", "AGENTS.md"], // emits both from one spec
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### `sections`
|
|
31
|
+
|
|
32
|
+
`Record<string, string | InstructionFragment[]>` -- Named prose sections. Each key becomes a `## Heading` in the compiled output (first letter uppercased). Values are either plain strings or tagged templates via `instructions` with embedded `file()`, `cmd()`, and `ref()` references.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
sections: {
|
|
36
|
+
architecture: `Two rule types: enforce() and guidance().`,
|
|
37
|
+
setup: instructions`See ${file("docs/setup.md")} and run ${cmd("npm install")}.`,
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `keyFiles`
|
|
42
|
+
|
|
43
|
+
`Record<string, string>` -- File paths mapped to descriptions. Each path is verified via `existsSync` at compile time. Compiles to a bullet list under `## Key Files`.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
keyFiles: {
|
|
47
|
+
"src/spec.ts": "Type system and builder functions",
|
|
48
|
+
"src/compile.ts": "Compiler: spec to markdown with SHA-256 hash",
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### `commands`
|
|
53
|
+
|
|
54
|
+
`Record<string, string>` -- Commands mapped to descriptions. `npm run <script>` and `npm <lifecycle>` commands are verified against `package.json` scripts at compile time. Compiles to a bullet list under `## Commands`.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
commands: {
|
|
58
|
+
"npm run build": "Compile TypeScript to dist/",
|
|
59
|
+
"npm test": "Build and run all tests",
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `rules`
|
|
64
|
+
|
|
65
|
+
`Record<string, Rule>` -- Rule ID mapped to an `enforce()` or `guidance()` rule. The ID is kebab-cased by convention and is converted to a Title Case `### Heading` in compiled output. See [Rule Types](#rule-types) below.
|
|
66
|
+
|
|
67
|
+
## SKILL.md Specs
|
|
68
|
+
|
|
69
|
+
Use `skill()` to define a SKILL.md spec. Compiles to markdown with YAML frontmatter.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { skill, file, cmd, ref, instructions } from "vigiles";
|
|
73
|
+
|
|
74
|
+
export default skill({
|
|
75
|
+
name: "pr-to-lint-rule",
|
|
76
|
+
description: "Convert PR feedback into an automated lint rule",
|
|
77
|
+
argumentHint: "<description of recurring PR feedback>",
|
|
78
|
+
disableModelInvocation: true,
|
|
79
|
+
body: instructions`
|
|
80
|
+
Check ${file("eslint.config.ts")} for existing rules.
|
|
81
|
+
Run ${cmd("npm test")} to verify.
|
|
82
|
+
See ${ref("skills/other/SKILL.md")} for format.
|
|
83
|
+
`,
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Field | Type | Required | Description |
|
|
88
|
+
| ------------------------ | --------------------------------- | -------- | --------------------------------------------------- |
|
|
89
|
+
| `name` | `string` | yes | Skill name (used in YAML frontmatter) |
|
|
90
|
+
| `description` | `string` | yes | Short description (frontmatter) |
|
|
91
|
+
| `argumentHint` | `string` | no | Hint for the argument (frontmatter) |
|
|
92
|
+
| `disableModelInvocation` | `boolean` | no | Disable model invocation flag (frontmatter) |
|
|
93
|
+
| `body` | `string \| InstructionFragment[]` | yes | Instruction body -- plain string or tagged template |
|
|
94
|
+
|
|
95
|
+
## Reference Helpers
|
|
96
|
+
|
|
97
|
+
Reference helpers create branded types that the compiler validates at compile time.
|
|
98
|
+
|
|
99
|
+
### `file(path)`
|
|
100
|
+
|
|
101
|
+
Returns a `FileRef` containing a `VerifiedPath`. The path is verified to exist via `existsSync` at compile time. Compiles to a backtick path in markdown: `` `path/to/file.ts` ``.
|
|
102
|
+
|
|
103
|
+
### `cmd(command)`
|
|
104
|
+
|
|
105
|
+
Returns a `CmdRef` containing a `VerifiedCmd`. For npm commands, the script is verified against `package.json` at compile time. Compiles to a backtick command: `` `npm run build` ``.
|
|
106
|
+
|
|
107
|
+
### `ref(path)`
|
|
108
|
+
|
|
109
|
+
Returns a `SkillRef` containing a `VerifiedRef`. The path is verified to exist at compile time. Compiles to a markdown link: `[dirname](path)`.
|
|
110
|
+
|
|
111
|
+
### `instructions`
|
|
112
|
+
|
|
113
|
+
Tagged template literal that interleaves strings and refs. Use it for `sections` values in `claude()` or the `body` of `skill()`.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
instructions`Check ${file("tsconfig.json")} then run ${cmd("npm test")}.`;
|
|
117
|
+
// Returns InstructionFragment[] -- the compiler renders and validates each ref.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Rule Types
|
|
121
|
+
|
|
122
|
+
### `enforce(linterRule, why)`
|
|
123
|
+
|
|
124
|
+
Declares a rule delegated to an external linter. The `linterRule` accepts template literal types:
|
|
125
|
+
|
|
126
|
+
- `${BuiltinLinter}/${string}` where BuiltinLinter is `eslint`, `stylelint`, `ruff`, `clippy`, `pylint`, or `rubocop`
|
|
127
|
+
- `@${scope}/${rule}` for scoped ESLint plugins (e.g., `@typescript-eslint/no-explicit-any`)
|
|
128
|
+
|
|
129
|
+
At compile time, vigiles verifies the rule exists in the linter's catalog and is enabled in the project's config. Compiles to `**Enforced by:** ` followed by the linter rule in backticks.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
rules: {
|
|
133
|
+
"no-console-log": enforce("eslint/no-console", "Use structured logger."),
|
|
134
|
+
"no-print": enforce("ruff/T201", "Use logging module."),
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `guidance(text)`
|
|
139
|
+
|
|
140
|
+
Declares a prose-only rule with no mechanical enforcement. Guidance rules still participate in the monotonicity proof system: once a rule exists, it can be strengthened ( `guidance` → `enforce` ) but never weakened or removed without an explicit allowlist.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
rules: {
|
|
144
|
+
"prefer-composition": guidance("Prefer composition over inheritance."),
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Compiles to: `**Guidance only** -- <text>`.
|
|
149
|
+
|
|
150
|
+
## Branded Types
|
|
151
|
+
|
|
152
|
+
`VerifiedPath`, `VerifiedCmd`, and `VerifiedRef` are branded string types (`string & { readonly [__brand]: "..." }`). They distinguish compiler-verified references from raw strings.
|
|
153
|
+
|
|
154
|
+
- `file()` produces `FileRef` containing `VerifiedPath`
|
|
155
|
+
- `cmd()` produces `CmdRef` containing `VerifiedCmd`
|
|
156
|
+
- `ref()` produces `SkillRef` containing `VerifiedRef`
|
|
157
|
+
|
|
158
|
+
The compiler only accepts these branded types in path-sensitive positions. This prevents passing unverified strings where a verified reference is expected -- the TypeScript compiler catches the error at authoring time.
|
|
159
|
+
|
|
160
|
+
## Configuration
|
|
161
|
+
|
|
162
|
+
Create `vigiles.config.ts` with `defineConfig()`:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { defineConfig } from "vigiles";
|
|
166
|
+
|
|
167
|
+
export default defineConfig({
|
|
168
|
+
specs: "**/*.spec.ts", // glob pattern for spec discovery (default: "**/*.spec.ts")
|
|
169
|
+
discover: true, // auto-discover linter rules for coverage reporting
|
|
170
|
+
maxRules: 50, // maximum rules per spec file
|
|
171
|
+
maxTokens: 2000, // maximum estimated tokens for compiled output (~4 chars/token)
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
| Option | Type | Description |
|
|
176
|
+
| ----------- | --------- | ------------------------------------------------------- |
|
|
177
|
+
| `specs` | `string` | Glob pattern to discover spec files |
|
|
178
|
+
| `discover` | `boolean` | Auto-discover linter rules for coverage reporting |
|
|
179
|
+
| `maxRules` | `number` | Compilation fails if a spec exceeds this rule count |
|
|
180
|
+
| `maxTokens` | `number` | Compilation fails if estimated tokens exceed this limit |
|
|
181
|
+
|
|
182
|
+
## Hash Verification
|
|
183
|
+
|
|
184
|
+
Every compiled file starts with a SHA-256 integrity hash comment:
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
<!-- vigiles:sha256:a1b2c3d4e5f67890 compiled from CLAUDE.md.spec.ts -->
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The hash covers the full compiled content (excluding the hash line itself), truncated to 16 hex characters.
|
|
191
|
+
|
|
192
|
+
### `vigiles audit`
|
|
193
|
+
|
|
194
|
+
Verifies that each compiled file's hash matches its content, reports linter rule coverage gaps, and suggests guidance rules that could be upgraded to `enforce()`. If someone manually edits the markdown, the hash will no longer match, and `vigiles audit` reports the file as modified. This ensures the spec remains the source of truth.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import eslint from "@eslint/js";
|
|
2
|
+
import tseslint from "@typescript-eslint/eslint-plugin";
|
|
3
|
+
import tsparser from "@typescript-eslint/parser";
|
|
4
|
+
import sonarjs from "eslint-plugin-sonarjs";
|
|
5
|
+
import globals from "globals";
|
|
6
|
+
|
|
7
|
+
export default [
|
|
8
|
+
{
|
|
9
|
+
ignores: ["dist/", "node_modules/"],
|
|
10
|
+
},
|
|
11
|
+
eslint.configs.recommended,
|
|
12
|
+
{
|
|
13
|
+
files: ["src/**/*.ts"],
|
|
14
|
+
languageOptions: {
|
|
15
|
+
parser: tsparser,
|
|
16
|
+
globals: {
|
|
17
|
+
...globals.node,
|
|
18
|
+
},
|
|
19
|
+
parserOptions: {
|
|
20
|
+
projectService: true,
|
|
21
|
+
tsconfigRootDir: import.meta.dirname,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
plugins: {
|
|
25
|
+
"@typescript-eslint": tseslint,
|
|
26
|
+
sonarjs,
|
|
27
|
+
},
|
|
28
|
+
rules: {
|
|
29
|
+
...tseslint.configs["strict-type-checked"]?.rules,
|
|
30
|
+
// TypeScript handles these better than ESLint
|
|
31
|
+
"no-undef": "off",
|
|
32
|
+
"no-unused-vars": "off",
|
|
33
|
+
// Allow unused vars prefixed with _
|
|
34
|
+
"@typescript-eslint/no-unused-vars": [
|
|
35
|
+
"error",
|
|
36
|
+
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
|
37
|
+
],
|
|
38
|
+
// We use createRequire legitimately for linter detection
|
|
39
|
+
"@typescript-eslint/no-require-imports": "off",
|
|
40
|
+
// Relax some strict rules that are too noisy for this codebase
|
|
41
|
+
"@typescript-eslint/restrict-template-expressions": "off",
|
|
42
|
+
"@typescript-eslint/no-unnecessary-condition": "off",
|
|
43
|
+
// Ban non-null assertions — use proper narrowing instead
|
|
44
|
+
"@typescript-eslint/no-non-null-assertion": "error",
|
|
45
|
+
|
|
46
|
+
// --- Complexity rules ---
|
|
47
|
+
complexity: ["warn", { max: 15 }],
|
|
48
|
+
"max-depth": ["warn", { max: 4 }],
|
|
49
|
+
"max-lines-per-function": [
|
|
50
|
+
"warn",
|
|
51
|
+
{ max: 80, skipBlankLines: true, skipComments: true },
|
|
52
|
+
],
|
|
53
|
+
"max-params": ["warn", { max: 4 }],
|
|
54
|
+
|
|
55
|
+
// --- SonarJS ---
|
|
56
|
+
"sonarjs/cognitive-complexity": ["warn", 15],
|
|
57
|
+
"sonarjs/no-duplicate-string": ["warn", { threshold: 4 }],
|
|
58
|
+
"sonarjs/no-identical-functions": "warn",
|
|
59
|
+
"sonarjs/no-duplicated-branches": "warn",
|
|
60
|
+
"sonarjs/no-identical-conditions": "error",
|
|
61
|
+
"sonarjs/no-identical-expressions": "error",
|
|
62
|
+
"sonarjs/no-nested-conditional": "warn",
|
|
63
|
+
"sonarjs/nested-control-flow": ["warn", { maximumNestingLevel: 3 }],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
// Test files: relax promise, assertion, and complexity rules
|
|
67
|
+
{
|
|
68
|
+
files: ["src/**/*.test.ts"],
|
|
69
|
+
rules: {
|
|
70
|
+
// node:test describe/it return promises that don't need to be awaited
|
|
71
|
+
"@typescript-eslint/no-floating-promises": "off",
|
|
72
|
+
"@typescript-eslint/no-unnecessary-type-assertion": "off",
|
|
73
|
+
// Tests are naturally longer and more repetitive
|
|
74
|
+
"max-lines-per-function": "off",
|
|
75
|
+
"sonarjs/no-duplicate-string": "off",
|
|
76
|
+
"sonarjs/no-identical-functions": "off",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!-- vigiles:sha256:f683550dbdfab721 compiled from examples/CLAUDE.md.spec.ts -->
|
|
2
|
+
|
|
3
|
+
# CLAUDE.md
|
|
4
|
+
|
|
5
|
+
## Positioning
|
|
6
|
+
|
|
7
|
+
vigiles compiles `.spec.ts` files to instruction files (CLAUDE.md, AGENTS.md, or any markdown target). The spec is the source of truth. The markdown is a build artifact.
|
|
8
|
+
|
|
9
|
+
The linter cross-referencing engine is the core moat: `enforce("@typescript-eslint/no-floating-promises")` verifies the rule exists AND is enabled in your linter config. Same for ESLint, Ruff, Clippy, Pylint, RuboCop, and Stylelint.
|
|
10
|
+
|
|
11
|
+
`generate-types` is the second moat: scans all 6 linter APIs, package.json, and project files to emit a `.d.ts` with type unions. The TS compiler then PROVES references are valid at authoring time — typos become type errors, not runtime surprises.
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
Three rule types in specs: `enforce()` (delegated to external tool), `check()` (vigiles-owned filesystem assertion), `guidance()` (prose only).
|
|
16
|
+
|
|
17
|
+
Core modules: `src/spec.ts` (types + builders), `src/compile.ts` (compiler), `src/linters.ts` (6-linter cross-referencing engine), `src/generate-types.ts` (type generator).
|
|
18
|
+
|
|
19
|
+
## Key Files
|
|
20
|
+
|
|
21
|
+
- `src/spec.ts` — Type system and builder functions
|
|
22
|
+
- `src/compile.ts` — Compiler: spec → markdown with SHA-256 hash
|
|
23
|
+
- `src/linters.ts` — Linter cross-referencing engine (6 linters)
|
|
24
|
+
- `src/generate-types.ts` — Type generator: project state → .d.ts
|
|
25
|
+
- `src/cli.ts` — CLI: compile, check, init, generate-types, discover, adopt
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
- `npm run build` — Compile TypeScript to dist/
|
|
30
|
+
- `npm test` — Build and run all tests
|
|
31
|
+
- `npm run fmt` — Format with prettier
|
|
32
|
+
- `npm run fmt:check` — Check formatting
|
|
33
|
+
|
|
34
|
+
## Rules
|
|
35
|
+
|
|
36
|
+
### Zero Config By Default
|
|
37
|
+
|
|
38
|
+
**Guidance only** — vigiles compile should work with just a .spec.ts file. Config exists only for overrides (maxRules, maxTokens, catalogOnly).
|
|
39
|
+
|
|
40
|
+
### Never Skip Tests
|
|
41
|
+
|
|
42
|
+
**Guidance only** — All tests must pass. If a test requires a CLI tool (pylint, rubocop, ruff, clippy), install the tool, don't skip the test.
|
|
43
|
+
|
|
44
|
+
### Dont Reimplement Linters
|
|
45
|
+
|
|
46
|
+
**Guidance only** — Architectural linting belongs in ast-grep/Dependency Cruiser/Steiger. Per-file code rules belong in ESLint/Ruff/Clippy. vigiles owns: compilation, linter cross-referencing, type generation, filesystem assertions, and stale reference detection.
|
|
47
|
+
|
|
48
|
+
### Format Before Commit
|
|
49
|
+
|
|
50
|
+
**Guidance only** — Run `npm run fmt:check` before committing. Inline code spans in markdown need surrounding spaces to render correctly.
|
|
51
|
+
|
|
52
|
+
### No Session Links
|
|
53
|
+
|
|
54
|
+
**Guidance only** — This is a public repo. Claude Code session URLs are private and must not appear in commits or PRs.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: CLAUDE.md specification for the vigiles project itself.
|
|
3
|
+
*
|
|
4
|
+
* This is the source of truth. CLAUDE.md is a compiled build artifact.
|
|
5
|
+
* Run `vigiles compile` to generate CLAUDE.md from this spec.
|
|
6
|
+
*/
|
|
7
|
+
import { claude, guidance } from "../src/spec.js";
|
|
8
|
+
|
|
9
|
+
export default claude({
|
|
10
|
+
sections: {
|
|
11
|
+
positioning: `vigiles compiles \`.spec.ts\` files to instruction files (CLAUDE.md, AGENTS.md, or any markdown target). The spec is the source of truth. The markdown is a build artifact.
|
|
12
|
+
|
|
13
|
+
The linter cross-referencing engine is the core moat: \`enforce("@typescript-eslint/no-floating-promises")\` verifies the rule exists AND is enabled in your linter config. Same for ESLint, Ruff, Clippy, Pylint, RuboCop, and Stylelint.
|
|
14
|
+
|
|
15
|
+
\`generate-types\` is the second moat: scans all 6 linter APIs, package.json, and project files to emit a \`.d.ts\` with type unions. The TS compiler then PROVES references are valid at authoring time — typos become type errors, not runtime surprises.`,
|
|
16
|
+
|
|
17
|
+
architecture: `Three rule types in specs: \`enforce()\` (delegated to external tool), \`check()\` (vigiles-owned filesystem assertion), \`guidance()\` (prose only).
|
|
18
|
+
|
|
19
|
+
Core modules: \`src/spec.ts\` (types + builders), \`src/compile.ts\` (compiler), \`src/linters.ts\` (6-linter cross-referencing engine), \`src/generate-types.ts\` (type generator).`,
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
keyFiles: {
|
|
23
|
+
"src/spec.ts": "Type system and builder functions",
|
|
24
|
+
"src/compile.ts": "Compiler: spec → markdown with SHA-256 hash",
|
|
25
|
+
"src/linters.ts": "Linter cross-referencing engine (6 linters)",
|
|
26
|
+
"src/generate-types.ts": "Type generator: project state → .d.ts",
|
|
27
|
+
"src/cli.ts": "CLI: compile, check, init, generate-types, discover, adopt",
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
commands: {
|
|
31
|
+
"npm run build": "Compile TypeScript to dist/",
|
|
32
|
+
"npm test": "Build and run all tests",
|
|
33
|
+
"npm run fmt": "Format with prettier",
|
|
34
|
+
"npm run fmt:check": "Check formatting",
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
rules: {
|
|
38
|
+
"zero-config-by-default": guidance(
|
|
39
|
+
"vigiles compile should work with just a .spec.ts file. Config exists only for overrides (maxRules, maxTokens, catalogOnly).",
|
|
40
|
+
),
|
|
41
|
+
|
|
42
|
+
"never-skip-tests": guidance(
|
|
43
|
+
"All tests must pass. If a test requires a CLI tool (pylint, rubocop, ruff, clippy), install the tool, don't skip the test.",
|
|
44
|
+
),
|
|
45
|
+
|
|
46
|
+
"dont-reimplement-linters": guidance(
|
|
47
|
+
"Architectural linting belongs in ast-grep/Dependency Cruiser/Steiger. Per-file code rules belong in ESLint/Ruff/Clippy. vigiles owns: compilation, linter cross-referencing, type generation, filesystem assertions, and stale reference detection.",
|
|
48
|
+
),
|
|
49
|
+
|
|
50
|
+
"format-before-commit": guidance(
|
|
51
|
+
"Run `npm run fmt:check` before committing. Inline code spans in markdown need surrounding spaces to render correctly.",
|
|
52
|
+
),
|
|
53
|
+
|
|
54
|
+
"no-session-links": guidance(
|
|
55
|
+
"This is a public repo. Claude Code session URLs are private and must not appear in commits or PRs.",
|
|
56
|
+
),
|
|
57
|
+
|
|
58
|
+
// Example: check() rule (commented out because this is a demo spec,
|
|
59
|
+
// not the project's actual spec — see root CLAUDE.md.spec.ts)
|
|
60
|
+
// "test-file-pairing": check(
|
|
61
|
+
// every("src/**/!(*test|*spec).ts").has("{name}.test.ts"),
|
|
62
|
+
// "Every source module should have a corresponding test file.",
|
|
63
|
+
// ),
|
|
64
|
+
},
|
|
65
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!-- vigiles:sha256:da271d5e41e8e90e compiled from examples/SKILL.md.spec.ts -->
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
name: pr-to-lint-rule
|
|
6
|
+
description: Convert a recurring PR review comment into an automated lint rule with tests and CLAUDE.md annotation
|
|
7
|
+
disable-model-invocation: true
|
|
8
|
+
argument-hint: <description of recurring PR feedback>
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Convert a recurring PR review comment into an automated lint rule.
|
|
13
|
+
|
|
14
|
+
## Arguments
|
|
15
|
+
|
|
16
|
+
$ARGUMENTS — A natural language description of the pattern to enforce. Examples:
|
|
17
|
+
|
|
18
|
+
- "we keep telling people not to import directly from antd, use our design system barrel file instead"
|
|
19
|
+
- "people forget to use our custom logger instead of console.log"
|
|
20
|
+
- "don't use unwrap() in production code, use expect() or proper error handling"
|
|
21
|
+
- "API route handlers must use the withAuth wrapper"
|
|
22
|
+
|
|
23
|
+
## Instructions
|
|
24
|
+
|
|
25
|
+
You are generating an automated lint rule from a recurring code review pattern. Follow these steps:
|
|
26
|
+
|
|
27
|
+
### Step 1: Detect the Project Language and Toolchain
|
|
28
|
+
|
|
29
|
+
Look at the repository to determine:
|
|
30
|
+
|
|
31
|
+
- **Primary language** (JS/TS, Python, Rust, Go, Ruby, etc.)
|
|
32
|
+
- **Linter in use** (ESLint, Ruff, Clippy, golangci-lint, RuboCop, etc.)
|
|
33
|
+
- **Testing framework** (Vitest, Jest, pytest, cargo test, etc.)
|
|
34
|
+
- **Existing custom rules** (to match conventions)
|
|
35
|
+
|
|
36
|
+
**If the language or linter cannot be confidently detected**, **ask the user** which language and linter to target before generating anything.
|
|
37
|
+
|
|
38
|
+
### Step 2: Generate the Lint Rule
|
|
39
|
+
|
|
40
|
+
Based on the detected (or user-specified) language, generate the appropriate rule type.
|
|
41
|
+
|
|
42
|
+
For JavaScript/TypeScript, generate an ESLint rule using the AST visitor pattern with `meta` and `create(context)`.
|
|
43
|
+
|
|
44
|
+
### Step 3: Update `CLAUDE.md`
|
|
45
|
+
|
|
46
|
+
Add the annotation block. See [enforce-rules-format](skills/enforce-rules-format/SKILL.md) for the correct format.
|
|
47
|
+
|
|
48
|
+
### Step 4: Verify
|
|
49
|
+
|
|
50
|
+
Run `npm test` to ensure the new rule and tests pass.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: SKILL.md specification for the pr-to-lint-rule skill.
|
|
3
|
+
*
|
|
4
|
+
* This is the source of truth. SKILL.md is a compiled build artifact.
|
|
5
|
+
* Run `vigiles compile` to generate SKILL.md from this spec.
|
|
6
|
+
*/
|
|
7
|
+
import { skill, file, cmd, ref, instructions } from "../src/spec.js";
|
|
8
|
+
|
|
9
|
+
export default skill({
|
|
10
|
+
name: "pr-to-lint-rule",
|
|
11
|
+
description:
|
|
12
|
+
"Convert a recurring PR review comment into an automated lint rule with tests and CLAUDE.md annotation",
|
|
13
|
+
disableModelInvocation: true,
|
|
14
|
+
argumentHint: "<description of recurring PR feedback>",
|
|
15
|
+
|
|
16
|
+
body: instructions`
|
|
17
|
+
Convert a recurring PR review comment into an automated lint rule.
|
|
18
|
+
|
|
19
|
+
## Arguments
|
|
20
|
+
|
|
21
|
+
$ARGUMENTS — A natural language description of the pattern to enforce. Examples:
|
|
22
|
+
|
|
23
|
+
- "we keep telling people not to import directly from antd, use our design system barrel file instead"
|
|
24
|
+
- "people forget to use our custom logger instead of console.log"
|
|
25
|
+
- "don't use unwrap() in production code, use expect() or proper error handling"
|
|
26
|
+
- "API route handlers must use the withAuth wrapper"
|
|
27
|
+
|
|
28
|
+
## Instructions
|
|
29
|
+
|
|
30
|
+
You are generating an automated lint rule from a recurring code review pattern. Follow these steps:
|
|
31
|
+
|
|
32
|
+
### Step 1: Detect the Project Language and Toolchain
|
|
33
|
+
|
|
34
|
+
Look at the repository to determine:
|
|
35
|
+
|
|
36
|
+
- **Primary language** (JS/TS, Python, Rust, Go, Ruby, etc.)
|
|
37
|
+
- **Linter in use** (ESLint, Ruff, Clippy, golangci-lint, RuboCop, etc.)
|
|
38
|
+
- **Testing framework** (Vitest, Jest, pytest, cargo test, etc.)
|
|
39
|
+
- **Existing custom rules** (to match conventions)
|
|
40
|
+
|
|
41
|
+
**If the language or linter cannot be confidently detected**, **ask the user** which language and linter to target before generating anything.
|
|
42
|
+
|
|
43
|
+
### Step 2: Generate the Lint Rule
|
|
44
|
+
|
|
45
|
+
Based on the detected (or user-specified) language, generate the appropriate rule type.
|
|
46
|
+
|
|
47
|
+
For JavaScript/TypeScript, generate an ESLint rule using the AST visitor pattern with \`meta\` and \`create(context)\`.
|
|
48
|
+
|
|
49
|
+
### Step 3: Update ${file("CLAUDE.md")}
|
|
50
|
+
|
|
51
|
+
Add the annotation block. See ${ref("skills/enforce-rules-format/SKILL.md")} for the correct format.
|
|
52
|
+
|
|
53
|
+
### Step 4: Verify
|
|
54
|
+
|
|
55
|
+
Run ${cmd("npm test")} to ensure the new rule and tests pass.
|
|
56
|
+
`,
|
|
57
|
+
});
|
package/logo.png
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vigiles",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Compile .spec.ts files to instruction files (CLAUDE.md, AGENTS.md) with linter cross-referencing",
|
|
5
|
+
"bin": {
|
|
6
|
+
"vigiles": "dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/spec.js",
|
|
9
|
+
"types": "./dist/spec.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/spec.js",
|
|
12
|
+
"./spec": "./dist/spec.js",
|
|
13
|
+
"./compile": "./dist/compile.js",
|
|
14
|
+
"./linters": "./dist/linters.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"test": "npm run build && node --test dist/spec.test.js dist/validate.test.js dist/cli.test.js dist/proofs.test.js dist/inline.test.js",
|
|
19
|
+
"lint": "eslint src/",
|
|
20
|
+
"fmt": "prettier --write .",
|
|
21
|
+
"fmt:check": "prettier --check ."
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@eslint/js": "^10.0.1",
|
|
25
|
+
"@types/minimatch": "^5.1.2",
|
|
26
|
+
"@types/node": "^20.19.39",
|
|
27
|
+
"@typescript-eslint/eslint-plugin": "^8.58.0",
|
|
28
|
+
"@typescript-eslint/parser": "^8.58.0",
|
|
29
|
+
"eslint": "^10.1.0",
|
|
30
|
+
"eslint-plugin-sonarjs": "^4.0.2",
|
|
31
|
+
"globals": "^17.4.0",
|
|
32
|
+
"prettier": "^3.8.1",
|
|
33
|
+
"tsx": "^4.21.0",
|
|
34
|
+
"typescript": "^5.9.3"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@jackchuka/mdschema": "^0.12.8",
|
|
38
|
+
"cosmiconfig": "^9.0.1",
|
|
39
|
+
"glob": "^13.0.6",
|
|
40
|
+
"minimatch": "^10.0.1"
|
|
41
|
+
}
|
|
42
|
+
}
|