gcf-aif-ext 0.2.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/LICENSE +21 -0
- package/README.md +41 -0
- package/commands/gcf.js +173 -0
- package/extension.json +24 -0
- package/hooks/pre-commit.js +91 -0
- package/injections/commit-status.md +6 -0
- package/injections/plan-context.md +8 -0
- package/injections/review-pillars.md +7 -0
- package/lib/anchors.js +109 -0
- package/lib/audit-core.js +102 -0
- package/lib/context.js +77 -0
- package/lib/frontmatter.js +26 -0
- package/lib/gcf-paths.js +60 -0
- package/lib/git.js +19 -0
- package/lib/id.js +42 -0
- package/lib/index-gen.js +51 -0
- package/lib/linter.js +61 -0
- package/lib/playtest-list.js +13 -0
- package/lib/safe-path.js +71 -0
- package/lib/transitions.js +30 -0
- package/lib/validate-card.js +48 -0
- package/lib/verdict-fs.js +126 -0
- package/node_modules/cnlp-kit/CHANGELOG.md +625 -0
- package/node_modules/cnlp-kit/LICENSE +21 -0
- package/node_modules/cnlp-kit/README.md +242 -0
- package/node_modules/cnlp-kit/bin/cnlp.js +334 -0
- package/node_modules/cnlp-kit/cnlp-format.md +470 -0
- package/node_modules/cnlp-kit/package.json +40 -0
- package/node_modules/cnlp-kit/profiles/adr.md +129 -0
- package/node_modules/cnlp-kit/profiles/api.md +87 -0
- package/node_modules/cnlp-kit/profiles/concept.md +70 -0
- package/node_modules/cnlp-kit/profiles/docset.md +103 -0
- package/node_modules/cnlp-kit/profiles/guide.md +51 -0
- package/node_modules/cnlp-kit/profiles/profile.md +69 -0
- package/node_modules/cnlp-kit/profiles/reference.md +61 -0
- package/node_modules/cnlp-kit/profiles/skill.md +75 -0
- package/node_modules/cnlp-kit/profiles/task.md +65 -0
- package/node_modules/cnlp-kit/quality-rules.md +69 -0
- package/node_modules/cnlp-kit/skills/cnlp-docset/SKILL.md +73 -0
- package/node_modules/cnlp-kit/skills/cnlp-migrate/SKILL.md +64 -0
- package/node_modules/cnlp-kit/src/cnlp.js +534 -0
- package/node_modules/cnlp-kit/src/config.js +352 -0
- package/node_modules/yaml/LICENSE +13 -0
- package/node_modules/yaml/README.md +172 -0
- package/node_modules/yaml/bin.mjs +11 -0
- package/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
- package/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
- package/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
- package/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
- package/node_modules/yaml/browser/dist/compose/composer.js +219 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
- package/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
- package/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
- package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +226 -0
- package/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
- package/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
- package/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
- package/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
- package/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
- package/node_modules/yaml/browser/dist/doc/Document.js +335 -0
- package/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
- package/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
- package/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
- package/node_modules/yaml/browser/dist/doc/directives.js +176 -0
- package/node_modules/yaml/browser/dist/errors.js +57 -0
- package/node_modules/yaml/browser/dist/index.js +17 -0
- package/node_modules/yaml/browser/dist/log.js +11 -0
- package/node_modules/yaml/browser/dist/nodes/Alias.js +118 -0
- package/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
- package/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
- package/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
- package/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
- package/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
- package/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
- package/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
- package/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
- package/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
- package/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
- package/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
- package/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
- package/node_modules/yaml/browser/dist/parse/cst.js +98 -0
- package/node_modules/yaml/browser/dist/parse/lexer.js +721 -0
- package/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
- package/node_modules/yaml/browser/dist/parse/parser.js +975 -0
- package/node_modules/yaml/browser/dist/public-api.js +102 -0
- package/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
- package/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
- package/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
- package/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
- package/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
- package/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
- package/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
- package/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
- package/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
- package/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
- package/node_modules/yaml/browser/dist/schema/tags.js +96 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +67 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
- package/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
- package/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +25 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
- package/node_modules/yaml/browser/dist/util.js +11 -0
- package/node_modules/yaml/browser/dist/visit.js +233 -0
- package/node_modules/yaml/browser/index.js +5 -0
- package/node_modules/yaml/browser/package.json +3 -0
- package/node_modules/yaml/dist/cli.d.ts +8 -0
- package/node_modules/yaml/dist/cli.mjs +201 -0
- package/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
- package/node_modules/yaml/dist/compose/compose-collection.js +90 -0
- package/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
- package/node_modules/yaml/dist/compose/compose-doc.js +45 -0
- package/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
- package/node_modules/yaml/dist/compose/compose-node.js +112 -0
- package/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
- package/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
- package/node_modules/yaml/dist/compose/composer.d.ts +63 -0
- package/node_modules/yaml/dist/compose/composer.js +224 -0
- package/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
- package/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
- package/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
- package/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
- package/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-end.js +39 -0
- package/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
- package/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +228 -0
- package/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
- package/node_modules/yaml/dist/compose/resolve-props.js +148 -0
- package/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
- package/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
- package/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
- package/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
- package/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
- package/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
- package/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
- package/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
- package/node_modules/yaml/dist/doc/Document.d.ts +141 -0
- package/node_modules/yaml/dist/doc/Document.js +337 -0
- package/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
- package/node_modules/yaml/dist/doc/anchors.js +76 -0
- package/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
- package/node_modules/yaml/dist/doc/applyReviver.js +57 -0
- package/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
- package/node_modules/yaml/dist/doc/createNode.js +90 -0
- package/node_modules/yaml/dist/doc/directives.d.ts +49 -0
- package/node_modules/yaml/dist/doc/directives.js +178 -0
- package/node_modules/yaml/dist/errors.d.ts +21 -0
- package/node_modules/yaml/dist/errors.js +62 -0
- package/node_modules/yaml/dist/index.d.ts +25 -0
- package/node_modules/yaml/dist/index.js +50 -0
- package/node_modules/yaml/dist/log.d.ts +3 -0
- package/node_modules/yaml/dist/log.js +19 -0
- package/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
- package/node_modules/yaml/dist/nodes/Alias.js +120 -0
- package/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
- package/node_modules/yaml/dist/nodes/Collection.js +151 -0
- package/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
- package/node_modules/yaml/dist/nodes/Node.js +40 -0
- package/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
- package/node_modules/yaml/dist/nodes/Pair.js +39 -0
- package/node_modules/yaml/dist/nodes/Scalar.d.ts +47 -0
- package/node_modules/yaml/dist/nodes/Scalar.js +27 -0
- package/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
- package/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
- package/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
- package/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
- package/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
- package/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
- package/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
- package/node_modules/yaml/dist/nodes/identity.js +53 -0
- package/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
- package/node_modules/yaml/dist/nodes/toJS.js +39 -0
- package/node_modules/yaml/dist/options.d.ts +350 -0
- package/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
- package/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
- package/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
- package/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
- package/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
- package/node_modules/yaml/dist/parse/cst-visit.js +99 -0
- package/node_modules/yaml/dist/parse/cst.d.ts +109 -0
- package/node_modules/yaml/dist/parse/cst.js +112 -0
- package/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
- package/node_modules/yaml/dist/parse/lexer.js +723 -0
- package/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
- package/node_modules/yaml/dist/parse/line-counter.js +41 -0
- package/node_modules/yaml/dist/parse/parser.d.ts +84 -0
- package/node_modules/yaml/dist/parse/parser.js +980 -0
- package/node_modules/yaml/dist/public-api.d.ts +44 -0
- package/node_modules/yaml/dist/public-api.js +107 -0
- package/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
- package/node_modules/yaml/dist/schema/Schema.js +39 -0
- package/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/map.js +19 -0
- package/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
- package/node_modules/yaml/dist/schema/common/null.js +17 -0
- package/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/seq.js +19 -0
- package/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/string.js +16 -0
- package/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/bool.js +21 -0
- package/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/float.js +47 -0
- package/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/int.js +42 -0
- package/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
- package/node_modules/yaml/dist/schema/core/schema.js +25 -0
- package/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
- package/node_modules/yaml/dist/schema/json/schema.js +64 -0
- package/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
- package/node_modules/yaml/dist/schema/tags.d.ts +48 -0
- package/node_modules/yaml/dist/schema/tags.js +99 -0
- package/node_modules/yaml/dist/schema/types.d.ts +92 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
- package/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
- package/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
- package/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
- package/node_modules/yaml/dist/stringify/stringify.js +132 -0
- package/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
- package/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
- package/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
- package/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
- package/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
- package/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
- package/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
- package/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
- package/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
- package/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
- package/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
- package/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
- package/node_modules/yaml/dist/test-events.d.ts +4 -0
- package/node_modules/yaml/dist/test-events.js +134 -0
- package/node_modules/yaml/dist/util.d.ts +16 -0
- package/node_modules/yaml/dist/util.js +28 -0
- package/node_modules/yaml/dist/visit.d.ts +102 -0
- package/node_modules/yaml/dist/visit.js +236 -0
- package/node_modules/yaml/package.json +97 -0
- package/node_modules/yaml/util.js +2 -0
- package/package.json +54 -0
- package/profiles/card.md +65 -0
- package/profiles/pillars.md +31 -0
- package/profiles/system.md +56 -0
- package/schemas/card.schema.json +30 -0
- package/skills/gcf-adopt/SKILL.md +41 -0
- package/skills/gcf-audit/SKILL.md +27 -0
- package/skills/gcf-card/SKILL.md +35 -0
- package/skills/gcf-debate/SKILL.md +33 -0
- package/skills/gcf-ingest/SKILL.md +38 -0
- package/skills/gcf-init/SKILL.md +32 -0
- package/skills/gcf-verdict/SKILL.md +32 -0
- package/skills/gcf-vision/SKILL.md +33 -0
- package/templates/PILLARS.md +8 -0
- package/templates/backlog.md +4 -0
- package/templates/card.md +34 -0
- package/templates/cnlp.config.json +8 -0
- package/templates/config.yml +8 -0
- package/templates/system.md +22 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
# CNL-P format — the standard
|
|
2
|
+
|
|
3
|
+
A way to write any document an agent has to read precisely. This file is the source of
|
|
4
|
+
truth for it. Extend it when the format grows; do not re-derive the rules from an existing
|
|
5
|
+
file.
|
|
6
|
+
|
|
7
|
+
**Every rule here holds for every document.** What differs between one kind of document and
|
|
8
|
+
another — its blocks, their order, whether it has headings — is declared in a *profile*
|
|
9
|
+
(§7), never branched inside a rule. The profiles that ship live in `profiles/`; a new kind of
|
|
10
|
+
document is added by writing a new profile, with no change to this file.
|
|
11
|
+
|
|
12
|
+
**What is enforced:** the checker reads the profile for the values and checks the mechanical
|
|
13
|
+
rules against it. Each profile's `enforcement:` block names the command or test that runs it;
|
|
14
|
+
`npx cnlp check` is the one that ships here. No checker can judge whether a bullet says
|
|
15
|
+
something useful — only whether the document is shaped and worded as declared.
|
|
16
|
+
|
|
17
|
+
## 1. What it is
|
|
18
|
+
|
|
19
|
+
A document written as structured `key:` blocks instead of prose. One idea per line, one
|
|
20
|
+
term per concept, no narrative connective tissue.
|
|
21
|
+
|
|
22
|
+
Prose leaves the reader to work out which sentence is intent, which is policy, which is
|
|
23
|
+
sequence and which is the shape of the answer — by inference, every time the document is
|
|
24
|
+
read. A `key:` block states which is which. That helps a document that is executed and a
|
|
25
|
+
document that is consulted for the same reason: neither reader should have to reconstruct
|
|
26
|
+
the structure before using the content. A human reviewer gets the same benefit — the file is
|
|
27
|
+
auditable in one pass.
|
|
28
|
+
|
|
29
|
+
## 2. Shape
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
---
|
|
33
|
+
name: … <- YAML frontmatter, untouched
|
|
34
|
+
description: …
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
first_key:
|
|
38
|
+
- one idea
|
|
39
|
+
- one idea
|
|
40
|
+
|
|
41
|
+
second_key:
|
|
42
|
+
1. first step
|
|
43
|
+
2. second step
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **Frontmatter is never rewritten.** It is the machine contract of the document kind; the
|
|
47
|
+
profile lists its fields in `frontmatter_fields:`.
|
|
48
|
+
- Sections are top-level `key:` lines in `lower_snake_case`.
|
|
49
|
+
- A section ends where the next **unindented** `key:` begins, or where the next `##`
|
|
50
|
+
heading begins. Anything indented belongs to the section above it.
|
|
51
|
+
- **Headings come from the profile.** A profile that declares `headings:` has an H1 title
|
|
52
|
+
and those `##` headings partitioning the body, and each block names the heading it sits
|
|
53
|
+
under. A profile that declares none has a flat body and the title lives in the
|
|
54
|
+
frontmatter.
|
|
55
|
+
|
|
56
|
+
## 3. Section forms
|
|
57
|
+
|
|
58
|
+
Every section is one of five forms. **The profile declares the form of each block**; mixing
|
|
59
|
+
forms inside one section is not allowed.
|
|
60
|
+
|
|
61
|
+
**scalar** — one value on the key line.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
mode: release_check
|
|
65
|
+
decision: use a shared external session store for all authenticated sessions
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**bullet-list** — the default. Unindented `- ` items.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
purpose:
|
|
72
|
+
- state what the document is loaded for, and when a reader reaches for it
|
|
73
|
+
- state which neighbouring document owns the topics this one does not
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**numbered-list** — unindented `N. ` items, ordered, for a sequence.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
workflow:
|
|
80
|
+
1. run `npx cnlp check`
|
|
81
|
+
2. fix the issues it reports
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**record-list** — repeated records. Each record opens with `- key: value` and continues
|
|
85
|
+
with **two-space-indented** keys. Every record in one section carries the keys the profile
|
|
86
|
+
lists in `record_keys:`, and may carry those in `optional_record_keys:`. A key the profile
|
|
87
|
+
does not declare is not added to a record: it is prose looking for a home.
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
steps:
|
|
91
|
+
- id: freeze
|
|
92
|
+
description: stop merges to the release branch
|
|
93
|
+
owner: release manager
|
|
94
|
+
|
|
95
|
+
- id: tag
|
|
96
|
+
description: tag the commit the build came from
|
|
97
|
+
owner: release manager
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**A blank line between records separates them for a reader and carries no meaning.** A checker
|
|
101
|
+
skips it, so the two spellings parse the same; it belongs between records that run to two lines or
|
|
102
|
+
more, where the only other boundary is a `- ` at the start of a line and a reader has to scan for
|
|
103
|
+
it. A list of one-line records is left tight, because the double spacing buys nothing there.
|
|
104
|
+
|
|
105
|
+
**keyed-block** — a fixed set of named sub-values, or one verbatim fenced block. Sub-keys
|
|
106
|
+
are **two-space-indented**; a fenced block sits directly under the key.
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
run_footer:
|
|
110
|
+
format: "✔ <check-name> · <files> files · <issues> issues"
|
|
111
|
+
source: `npx cnlp check`
|
|
112
|
+
|
|
113
|
+
report_format:
|
|
114
|
+
```text
|
|
115
|
+
| # | Suggestion | Verdict | Justification |
|
|
116
|
+
```
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Nesting is one level deep**, and only inside a record-list or a keyed-block. A bullet
|
|
120
|
+
never carries sub-bullets to hold a second thought — that is two bullets.
|
|
121
|
+
|
|
122
|
+
**A fenced block is opaque.** Its contents are verbatim data, not structure: the `id:` and
|
|
123
|
+
`type:` lines inside a `template_frontmatter:` fence are frontmatter fields of another
|
|
124
|
+
document, not CNL-P sections. Anything reading this format skips fenced regions before
|
|
125
|
+
looking for section keys.
|
|
126
|
+
|
|
127
|
+
**An empty block.** A required block with nothing in it carries `- none` and the reason:
|
|
128
|
+
`- none: no alternative was viable at this scale`. That single line is the whole content and
|
|
129
|
+
replaces the form the block would otherwise take, so a record-list or a keyed-block says it
|
|
130
|
+
the same way a bullet-list does. It is the *whole* content: `- none` next to a real item says
|
|
131
|
+
the block is both empty and not, and the checker rejects it. An optional block with nothing in
|
|
132
|
+
it is deleted rather than marked, so `- none` in one is also rejected. A key with no items at
|
|
133
|
+
all is neither, and the checker rejects that too.
|
|
134
|
+
|
|
135
|
+
A **scalar** has no empty form: it holds the one value the block exists for, so a scalar with
|
|
136
|
+
nothing to say means the document is unfinished, and the checker says so.
|
|
137
|
+
|
|
138
|
+
A block whose content is a fenced region is not empty — the fence is opaque, not absent.
|
|
139
|
+
|
|
140
|
+
## 4. Line rules
|
|
141
|
+
|
|
142
|
+
- One idea per bullet. A bullet carrying two obligations is two bullets.
|
|
143
|
+
- **Length: 150 characters is the target, 250 is the hard limit.** It applies to every line
|
|
144
|
+
outside a fence — a bullet, a step, a scalar value, a record sub-key — because each of them
|
|
145
|
+
holds one idea. The median across the
|
|
146
|
+
migrated corpus is 72. Past 150, check whether the line holds a condition, a reason and an action at once;
|
|
147
|
+
if so, split it. A line that is long only because one idea enumerates its parts is fine
|
|
148
|
+
and is not split — that is the `give per approach: a, b, c` case below. Past 250 the
|
|
149
|
+
line is compound whatever it claims, and the checker rejects it.
|
|
150
|
+
- Plain lowercase prose in the bullet. No `MUST` / `MUST NOT` ceremony, no line ids.
|
|
151
|
+
- A rule and its reason go on one line, separated by a colon:
|
|
152
|
+
`do not move files by hand: the command owns the atomic move`.
|
|
153
|
+
- An enumeration inside one idea is fine and is not two ideas:
|
|
154
|
+
`give per approach: consequences over 6–12 months, effect on coupling, hidden risks`.
|
|
155
|
+
- **Markdown code fences and tables stay** wherever content is verbatim or tabular: shell
|
|
156
|
+
commands, frontmatter examples, text inserted into another file, diagrams, report
|
|
157
|
+
templates. A format that cannot hold a shell command is useless here.
|
|
158
|
+
|
|
159
|
+
## 5. Lexicon
|
|
160
|
+
|
|
161
|
+
- Use plain, common words over rare or literary ones.
|
|
162
|
+
- One term per concept, and do not vary the word once it is chosen.
|
|
163
|
+
- One part of speech per term: a word used as both a verb and a noun is 2 terms wearing 1 name.
|
|
164
|
+
- No idioms, no slang, no figurative language.
|
|
165
|
+
- No word with several unrelated senses unless this file fixes the sense.
|
|
166
|
+
- A short concrete verb over an abstract one.
|
|
167
|
+
- Define a specialized term once, then reuse it verbatim.
|
|
168
|
+
|
|
169
|
+
**`check the corpus` and `run the check` are 2 terms.** One term per concept fixes which word names a
|
|
170
|
+
thing; one part of speech fixes which job that word does — here the action, and the program that
|
|
171
|
+
performs the action. Keep the part of speech the term already carries and rename the other use:
|
|
172
|
+
`check` the verb, `the checker` the program. The checker cannot enforce this — telling a verb from a
|
|
173
|
+
noun needs a parser, and it is regular expressions over lines — so it is what a review looks for first
|
|
174
|
+
in a converted document, where a word doing 2 jobs survives every mechanical rule above.
|
|
175
|
+
|
|
176
|
+
**Quoted text is data.** Anything inside quotes or backticks is a value the document carries
|
|
177
|
+
— a command, a name, a claim being reported — not a claim the document makes, so no rule
|
|
178
|
+
below applies to it. That is what lets a document name a bad argument in order to reject it:
|
|
179
|
+
`do not accept "faster to write" as justification`.
|
|
180
|
+
|
|
181
|
+
**Prohibition is always `do not`.** `never` is not used as a bullet opener — one concept,
|
|
182
|
+
one term. `never` inside a sentence, qualifying a clause, is fine
|
|
183
|
+
(`the operator decides, do not silently downgrade`).
|
|
184
|
+
|
|
185
|
+
**A threshold is a digit.** `exactly 1 primary decision`, `at least 2 viable approaches`,
|
|
186
|
+
`more than 1 non-archived plan` — the reader has to check the value against something, so
|
|
187
|
+
it is written as a value. A digit also stands out against lowercase prose, and reads as a
|
|
188
|
+
parameter rather than as a word that must first be mapped to a number.
|
|
189
|
+
|
|
190
|
+
A number that is not a threshold stays a word: inside an idiom (`two ways of doing one
|
|
191
|
+
thing is a real cost`), inside a hyphenated adjective (`one-line`, `two-space-indented`),
|
|
192
|
+
or as an ordinal (`first review`). Digits there read as a typo.
|
|
193
|
+
|
|
194
|
+
**A limit is written as a comparison.** The canonical form is
|
|
195
|
+
`<subject> <operator> <value> <unit>`: `open connections per client <= 2`,
|
|
196
|
+
`p95 latency <= 200 ms`. The phrase form leaves the reader to work out whether the bound is
|
|
197
|
+
inclusive; the operator makes the author decide once, at the time the decision is made.
|
|
198
|
+
|
|
199
|
+
- operators are `<`, `<=`, `>`, `>=`, `=`, `!=`, and a closed range written `2..5`.
|
|
200
|
+
- the subject and the unit stay attached: `open connections per client <= 2`, not a bare `<= 2`.
|
|
201
|
+
- do not invent a threshold nobody agreed: an unagreed "fast startup" stays prose and marks
|
|
202
|
+
the document unfinished; the rule formalizes a limit, it does not fabricate one.
|
|
203
|
+
- a number that is a determiner inside a sentence keeps the word-plus-digit form:
|
|
204
|
+
`present at least 2 viable approaches` is an instruction, not a limit to check a value against.
|
|
205
|
+
|
|
206
|
+
The checker rejects only the phrases whose bound is genuinely open — `no more than`,
|
|
207
|
+
`at most`, `up to`, `not exceeding`, `no fewer than`, `not less than`. `at least`,
|
|
208
|
+
`exactly`, `more than`, `fewer than` and `only` state their bound, so they are left to
|
|
209
|
+
review, the same split §4 makes between the 150-character target and the 250-character
|
|
210
|
+
hard limit.
|
|
211
|
+
|
|
212
|
+
**An unquantified comparative is not a decision.** A comparative claims a value the reader
|
|
213
|
+
cannot check, so it is replaced by the property and its bound, or dropped.
|
|
214
|
+
|
|
215
|
+
| Instead of | Write |
|
|
216
|
+
|---|---|
|
|
217
|
+
| better, faster, cleaner | the property and its bound: `p95 latency <= 200 ms` |
|
|
218
|
+
| significantly, substantially | the measured delta |
|
|
219
|
+
| flexible, scalable, extensible | the axis it varies on, with its bound |
|
|
220
|
+
| where possible, if needed, as appropriate | the condition that triggers it |
|
|
221
|
+
| should probably, we may want to | the decision, or move the line to the block that owns doubt |
|
|
222
|
+
|
|
223
|
+
**A nominalization is the verb it hides.** `make a decision` is `decide` carrying 2 extra words and
|
|
224
|
+
no verb of its own. The checker rejects the light-verb construction — `make`, `do`, `perform`,
|
|
225
|
+
`provide`, `carry out` or `give`, then an article, then an abstract noun — because that is the shape
|
|
226
|
+
prose keeps when a migration converts it a line at a time. It is the specific case the bullet
|
|
227
|
+
`a short concrete verb over an abstract one` states in general.
|
|
228
|
+
|
|
229
|
+
| Instead of | Write |
|
|
230
|
+
|---|---|
|
|
231
|
+
| make a decision | `decide` |
|
|
232
|
+
| do a check of, perform a check on | `check` |
|
|
233
|
+
| provide a description of | `describe` |
|
|
234
|
+
| carry out an inspection of | `inspect` |
|
|
235
|
+
| give a report on | `report` |
|
|
236
|
+
|
|
237
|
+
The noun alone is not the construction: `the decision` in the block that records one is the thing
|
|
238
|
+
itself, not a verb in hiding. Neither is a light verb carrying a real object — the noun has to close
|
|
239
|
+
the phrase or take a preposition, so `make a check pass` is a causative and stays. And the verb alone
|
|
240
|
+
is not the construction either: `make` stays canonical while `make a decision` is rejected, because
|
|
241
|
+
the construction is the defect, not the verb.
|
|
242
|
+
|
|
243
|
+
**Canonical verbs**, for a profile whose `mood:` is `imperative`: `run`, `read`, `inspect`,
|
|
244
|
+
`identify`, `name`, `cite`, `compare`, `present`, `ask`, `update`, `transition`, `report`,
|
|
245
|
+
`record`, `state`, `stop`, `verify`, `validate`, `copy`, `list`, `map`, `fill`, `pick`, `make`,
|
|
246
|
+
`rewrite`, `tick`, `measure`, `repeat`, `write`. The set binds the word that opens a step; what follows the
|
|
247
|
+
opener is the step's own prose, held by the rest of this section. A profile whose mood is
|
|
248
|
+
`declarative` states what is true instead of what to do, in the present tense, and the set does not
|
|
249
|
+
bind it.
|
|
250
|
+
|
|
251
|
+
**The set is closed, and it grows on the record.** A step needing an opener the set does not carry
|
|
252
|
+
adds that verb here, in the same change as the step: it does not borrow a synonym, and it does not
|
|
253
|
+
pass silently. The 11 verbs from `copy` on arrived that way, harvested from step openers already
|
|
254
|
+
written against a set authored for 1 other kind of document — which is what an unrecorded
|
|
255
|
+
addition costs, a set no reader can trust. Nothing enforces the set yet: the opening word of a
|
|
256
|
+
step is an intra-block line grammar, which is the `item_pattern:` item in `docs/BACKLOG.md`.
|
|
257
|
+
|
|
258
|
+
**An imperative document names the actor.** A body written to a profile whose `mood:` is
|
|
259
|
+
`imperative` is addressed to whoever executes it, so an agentless passive drops the one word the
|
|
260
|
+
reader needs: `the profile is edited first` does not say who edits it, and `edit the profile first`
|
|
261
|
+
does. The checker reads a form of `be` followed by an `-ed` participle, and only in an imperative
|
|
262
|
+
document.
|
|
263
|
+
|
|
264
|
+
- a `declarative` document is left alone: it states what is true, and the actor of `a reference file
|
|
265
|
+
is loaded by path` is genuinely nobody.
|
|
266
|
+
- a participle naming a state rather than an action is exempt: `is required`, `is allowed`,
|
|
267
|
+
`is needed`, `is based on`, `is limited to`, `is intended`, `is expected`, and any `un-` participle
|
|
268
|
+
such as `is unchanged`.
|
|
269
|
+
- an irregular participle — `is written`, `is taken`, `is known` — is left to review, because `often`
|
|
270
|
+
and `open` end in the same 2 letters and are not participles at all. That is the same split this
|
|
271
|
+
section makes between the enforced and the reviewed half of a limit.
|
|
272
|
+
|
|
273
|
+
This is the 1 rule `mood:` gates; §7 states which of a profile's values the checker reads.
|
|
274
|
+
|
|
275
|
+
Deny-list, each with its replacement:
|
|
276
|
+
|
|
277
|
+
| Instead of | Write |
|
|
278
|
+
|---|---|
|
|
279
|
+
| surface | `state` or `report` |
|
|
280
|
+
| sharpen | `improve` |
|
|
281
|
+
| weigh | `compare` |
|
|
282
|
+
| leverage | `use` |
|
|
283
|
+
| ensure | `verify` |
|
|
284
|
+
| handle | name the action |
|
|
285
|
+
| robust | name the property |
|
|
286
|
+
| sanity-check | `check` |
|
|
287
|
+
|
|
288
|
+
Established domain terms are not on the deny-list even when they read as figures of
|
|
289
|
+
speech: `blast radius` is precise here and stays.
|
|
290
|
+
|
|
291
|
+
The checker enforces every row of this table except `handle`: the noun is an established
|
|
292
|
+
domain term — an entity handle, a file handle — that a word-level pattern cannot tell from the
|
|
293
|
+
verb, so the verb is left to review. That is the same enforced-versus-reviewed split the
|
|
294
|
+
passive rule states for irregular participles.
|
|
295
|
+
|
|
296
|
+
## 6. No section restates another
|
|
297
|
+
|
|
298
|
+
Each fact appears once, in the block that owns it.
|
|
299
|
+
|
|
300
|
+
- **The frontmatter is a block like any other.** A body line repeating a frontmatter field —
|
|
301
|
+
a code path, an id, a status, a link to another document — is a second copy that goes
|
|
302
|
+
stale. Naming another document inside a sentence is fine; carrying the relation there is
|
|
303
|
+
not.
|
|
304
|
+
- A rule already stated in one block is not repeated in another, and the step that checks it
|
|
305
|
+
names the command, not the rule again.
|
|
306
|
+
- A block is dropped when another block already states its content.
|
|
307
|
+
- Two blocks that state *different* facts about one thing are not a repetition: when
|
|
308
|
+
something is emitted and what shape it has are two facts. Stating it a third time is the
|
|
309
|
+
repetition, and that one goes.
|
|
310
|
+
|
|
311
|
+
A profile adds the pairs its own blocks confuse, as `note:` on the sections concerned — for
|
|
312
|
+
example, a cost that is certain against a risk that may not happen.
|
|
313
|
+
|
|
314
|
+
This is the rule most often broken by a first draft. When a file grows during migration,
|
|
315
|
+
look here first.
|
|
316
|
+
|
|
317
|
+
## 7. The profile contract
|
|
318
|
+
|
|
319
|
+
A profile is itself a CNL-P document, in `profiles/<name>.md`. It declares values, never
|
|
320
|
+
rules — a rule in a profile is a rule that escaped this file.
|
|
321
|
+
|
|
322
|
+
**`format:` closes the loop.** This file states the rules and a profile states the values, so
|
|
323
|
+
the two are read together and neither is complete alone. A reader arriving at a profile —
|
|
324
|
+
sent there by a skill, or by the document kind it describes — follows `format:` to the rules
|
|
325
|
+
first, then reads the profile for the blocks they apply to.
|
|
326
|
+
|
|
327
|
+
**The path in `format:` names a document, not a location on the reader's disk.** The standard
|
|
328
|
+
and the profiles that ship with it live inside the package, which is not the root of the
|
|
329
|
+
project whose documents obey them. From a project, `npx cnlp print` writes the standard to
|
|
330
|
+
stdout, `npx cnlp print <profile>` writes a profile, and `--path` gives the resolved location
|
|
331
|
+
to open directly. A project that keeps its own profiles names them in `cnlp.config.json` and
|
|
332
|
+
they resolve before the packaged ones.
|
|
333
|
+
|
|
334
|
+
| Block | Form | Holds |
|
|
335
|
+
|---|---|---|
|
|
336
|
+
| `format` | scalar | the standard this profile's documents obey, by path |
|
|
337
|
+
| `mood` | scalar | `imperative` or `declarative` (§5) |
|
|
338
|
+
| `headings` | bullet-list | the `##` headings in order, or `- none` for a flat body |
|
|
339
|
+
| `frontmatter_fields` | bullet-list | the fields §6 forbids the body to restate |
|
|
340
|
+
| `lexicon_exempt` | bullet-list | frontmatter fields the lexicon does not police, with the reason |
|
|
341
|
+
| `sections` | record-list | the blocks, in order |
|
|
342
|
+
| `custom_sections` | bullet-list | names a document of this kind may add, or `- none` |
|
|
343
|
+
| `enforcement` | bullet-list | the command or test that checks this profile, and its severity |
|
|
344
|
+
|
|
345
|
+
A `sections:` record carries `key`, `form` (one of the five in §3) and `required`
|
|
346
|
+
(`yes`/`no`), plus `heading`, `record_keys`, `optional_record_keys`, `record_values` and `note`
|
|
347
|
+
where they apply. The order of the records is the order the blocks appear in, so a record's `heading:`
|
|
348
|
+
never names a heading earlier than the heading of the record before it. `required: yes` means the
|
|
349
|
+
document is not conformant without the block; a block that is needed only under some
|
|
350
|
+
condition is `required: no` with the condition in its `note:`, because that judgment is not
|
|
351
|
+
mechanical.
|
|
352
|
+
|
|
353
|
+
A `custom_sections:` name is a block the checker accepts but does not shape: reference
|
|
354
|
+
material, a vocabulary the document refers to, or behaviour owned by something else. Reuse a
|
|
355
|
+
name `custom_sections:` already carries before inventing one — a new name is a new place for prose to hide.
|
|
356
|
+
This is where a project's own vocabulary lives, and it is the block a project edits after
|
|
357
|
+
adopting a packaged profile.
|
|
358
|
+
|
|
359
|
+
**`record_values:` closes a record key to a vocabulary.** A profile writes
|
|
360
|
+
`record_values: status = covered, source, avoid`, and a second key follows a `;`. The checker holds
|
|
361
|
+
every record of that block to it, in a record-list and in a keyed-block alike; a key the profile
|
|
362
|
+
leaves out of `record_values:` stays open and nothing polices its value.
|
|
363
|
+
|
|
364
|
+
A vocabulary stated only in a `note:` is a vocabulary nothing reads, and the cost of that is not
|
|
365
|
+
even: it depends on what the absent value makes the reader do. A docset `catalog:` record whose
|
|
366
|
+
`status` is outside `covered`, `source` and `avoid` used to read as `covered`, sending a reader to
|
|
367
|
+
a `page:` that is not there — the value failed open, and the reader could not tell. Close a key
|
|
368
|
+
whose wrong value would be read as a right one; leave open a key whose values are a free list.
|
|
369
|
+
|
|
370
|
+
This is one half of the intra-block grammar a profile cannot otherwise declare. The other half — the
|
|
371
|
+
*shape* of a line rather than the value of a key — is not here: a profile still cannot state the form
|
|
372
|
+
of a step's opening word or of a bare list item, and those rules ship as review-only.
|
|
373
|
+
|
|
374
|
+
**Where a custom block sits** is decided by what it is, in every profile, and the test is
|
|
375
|
+
whether the reader needs it in hand or is sent to it:
|
|
376
|
+
|
|
377
|
+
- a **term** the steps use without introducing it goes *before* the block that uses it, so it
|
|
378
|
+
is read first: `glossary`, `severity_scale`, `confidence_scale`.
|
|
379
|
+
- a **case table** a step names at the point of use goes *after*: `severity_mapping`,
|
|
380
|
+
`block_choice`, `file_map`. The step that says "apply the one matching case in `file_map`"
|
|
381
|
+
resolves the reference itself, and moving the table above the steps buries them.
|
|
382
|
+
- **behaviour owned by something else**, or anything describing what happens after the run,
|
|
383
|
+
goes *after*: `report_format`, `test_runners`, `follow_up`.
|
|
384
|
+
|
|
385
|
+
A custom block is allowed only when its content is neither a rule, a step, nor an output —
|
|
386
|
+
those have declared blocks already. Placement is judgment: nothing enforces it.
|
|
387
|
+
|
|
388
|
+
**What the checker reads.** `headings`, `frontmatter_fields`, `sections` — `record_values:`
|
|
389
|
+
included — `custom_sections` and `mood` shape what the checker enforces. `format`, `lexicon_exempt`, `enforcement` and
|
|
390
|
+
every `note:` are for whoever writes the document: `lexicon_exempt` names frontmatter fields, which
|
|
391
|
+
the checker never reads. A profile states both kinds, and this table says which is which so neither is
|
|
392
|
+
mistaken for the other.
|
|
393
|
+
|
|
394
|
+
`mood` sits on both sides. It is the register a body is written in, which the checker cannot judge, and it
|
|
395
|
+
is what gates the 1 lexicon rule that depends on the register: only an imperative document has an
|
|
396
|
+
actor to name, so only an imperative document is held to the active voice (§5).
|
|
397
|
+
|
|
398
|
+
`profiles/profile.md` is the profile of a profile, so the same checker covers the profile
|
|
399
|
+
files themselves — including that one, which is checked against itself. Nothing checks that
|
|
400
|
+
last step; the recursion stops there on purpose.
|
|
401
|
+
|
|
402
|
+
## 8. Migration procedure
|
|
403
|
+
|
|
404
|
+
1. **List the source's rules before rewriting.** Every normative statement, including the
|
|
405
|
+
ones buried in a subordinate clause.
|
|
406
|
+
2. Rewrite into the profile's blocks.
|
|
407
|
+
3. **Tick each listed rule against the result.** Anything deliberately dropped is stated
|
|
408
|
+
out loud, with the reason.
|
|
409
|
+
4. **Check the reverse direction.** Any rule in the result that has no origin in the
|
|
410
|
+
source is an addition — say so; do not let it arrive silently.
|
|
411
|
+
5. **Measure the size and state the cause of any growth.** Growth is not automatically a
|
|
412
|
+
defect: splitting one prose sentence into the three obligations it was hiding
|
|
413
|
+
legitimately adds lines, and so does fixing a defect found during the rewrite. Growth
|
|
414
|
+
with no such cause means §6 was broken or prose survived.
|
|
415
|
+
6. Run the command the profile's `enforcement:` names.
|
|
416
|
+
|
|
417
|
+
Formalizing prose finds defects in the prose — contradictory rules, branches that turn out
|
|
418
|
+
not to be mutually exclusive, instructions the tooling rejects. Report them; do not resolve
|
|
419
|
+
them silently inside a format migration.
|
|
420
|
+
|
|
421
|
+
## 9. Page references
|
|
422
|
+
|
|
423
|
+
Everything above holds for one document alone. A **document set** is a directory of documents
|
|
424
|
+
written to be read a page at a time, where one page sends the reader to another instead of
|
|
425
|
+
restating it — which is §6 applied across files rather than inside one.
|
|
426
|
+
|
|
427
|
+
A reference is a **`page:` or a `by:` value**, and a `docset:` value names a whole set. They are
|
|
428
|
+
the only forms a reference takes: a name inside prose is a mention, not a reference, and nothing
|
|
429
|
+
resolves it. That is what lets a document set be checked at all.
|
|
430
|
+
|
|
431
|
+
**A page id is `<set>:<name>`.** `<name>` is the path under the set's directory without the `.md`
|
|
432
|
+
suffix, so `docsets/toolkit/api/query.md` in a `docsets` root of `docsets` is
|
|
433
|
+
`toolkit:api/query`. **A bare name is local**, resolved against the set the referring document
|
|
434
|
+
sits in; a name carrying a prefix crosses into another set. A bare name is the common case, and
|
|
435
|
+
the prefix is what makes a crossing visible at the point it happens.
|
|
436
|
+
|
|
437
|
+
The rules a checker holds:
|
|
438
|
+
|
|
439
|
+
- every `page:` and `by:` value resolves to a document that exists.
|
|
440
|
+
- every `docset:` value names a set that exists.
|
|
441
|
+
- a `by:` value is local, so it carries no prefix: `by:` names the replacement inside the set that
|
|
442
|
+
declares it, and a prefix there is a copy of the set's own name.
|
|
443
|
+
- every document except a set's `index` is the target of at least 1 reference. A page nothing
|
|
444
|
+
reaches is a page nobody reads, and it goes stale unobserved.
|
|
445
|
+
|
|
446
|
+
**The checker reads this only when a project says where its sets live**, with a `docsets` key in
|
|
447
|
+
`cnlp.config.json` naming the directory whose immediate subdirectories are one set each. A project
|
|
448
|
+
with no such key has no document set, and none of this applies to it.
|
|
449
|
+
|
|
450
|
+
The rules a review holds. They are the same kind of rule as the four above and no checker reaches
|
|
451
|
+
them, the way §5 splits the enforced half of a limit from the reviewed half:
|
|
452
|
+
|
|
453
|
+
- **A fact stated on one page of a set is not restated on another.** §6 holds inside a document and
|
|
454
|
+
stops at its edge; a set is many documents written to send the reader onward instead of repeating,
|
|
455
|
+
so the same rule holds across it. Two pages stating one fact go stale apart, and the reader who
|
|
456
|
+
found the stale copy has no way to know the other exists.
|
|
457
|
+
- **A set has one running example.** Whatever a page's own `example:` shows, it uses the names the
|
|
458
|
+
set's entry document introduced, so a reader assembling several pages gets one program rather than
|
|
459
|
+
fragments to reconcile. Fragments that do not compose put the reconciling on the reader, which is
|
|
460
|
+
the work a set exists to remove.
|
|
461
|
+
- **A name the entry document routes on reaches exactly one page.** A name listed against two pages
|
|
462
|
+
is two answers with nothing to choose between them, and the reader picks by opening both — which
|
|
463
|
+
is the guessing an entry document exists to end.
|
|
464
|
+
|
|
465
|
+
All three were found by reading a set, not by running one: a fact duplicated across two pages, one
|
|
466
|
+
set whose pages carried seven unrelated sets of names, and one name routed to three pages at once.
|
|
467
|
+
|
|
468
|
+
A profile decides which of its blocks carry references and what each reference means — an
|
|
469
|
+
`overrides:` record whose `page:` names a foreign page is a value that profile declares, not a rule
|
|
470
|
+
this file states.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cnlp-kit",
|
|
3
|
+
"version": "3.4.1",
|
|
4
|
+
"description": "CNL-P — a format for documents an agent has to read precisely. The standard, its profiles, and the checker that holds them.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "VirtualMaestro",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"cnlp",
|
|
10
|
+
"controlled-natural-language",
|
|
11
|
+
"agent",
|
|
12
|
+
"skill",
|
|
13
|
+
"adr",
|
|
14
|
+
"documentation",
|
|
15
|
+
"linter"
|
|
16
|
+
],
|
|
17
|
+
"bin": {
|
|
18
|
+
"cnlp": "bin/cnlp.js"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./src/cnlp.js",
|
|
22
|
+
"./config": "./src/config.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin",
|
|
26
|
+
"src",
|
|
27
|
+
"profiles",
|
|
28
|
+
"skills",
|
|
29
|
+
"cnlp-format.md",
|
|
30
|
+
"quality-rules.md",
|
|
31
|
+
"CHANGELOG.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "node --test",
|
|
35
|
+
"prepublishOnly": "node --test"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=22.14"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adr
|
|
3
|
+
description: The CNL-P profile of an architecture decision record — the blocks an ADR body uses, in order.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
format: cnlp-format.md
|
|
7
|
+
|
|
8
|
+
mood: declarative
|
|
9
|
+
|
|
10
|
+
headings:
|
|
11
|
+
- ## Context
|
|
12
|
+
- ## Decision
|
|
13
|
+
- ## Alternatives considered
|
|
14
|
+
- ## Consequences
|
|
15
|
+
|
|
16
|
+
frontmatter_fields:
|
|
17
|
+
- id
|
|
18
|
+
- type
|
|
19
|
+
- status
|
|
20
|
+
- owners
|
|
21
|
+
- depends_on
|
|
22
|
+
- affects
|
|
23
|
+
- supersedes
|
|
24
|
+
- code
|
|
25
|
+
- issue
|
|
26
|
+
- plan
|
|
27
|
+
- evidence
|
|
28
|
+
- replaced_by
|
|
29
|
+
|
|
30
|
+
lexicon_exempt:
|
|
31
|
+
- none: every field of an ADR body is read by a person deciding whether the rule applies
|
|
32
|
+
|
|
33
|
+
sections:
|
|
34
|
+
- key: problem
|
|
35
|
+
form: bullet-list
|
|
36
|
+
required: yes
|
|
37
|
+
heading: "## Context"
|
|
38
|
+
note: what breaks today, one observable statement per line
|
|
39
|
+
|
|
40
|
+
- key: constraints
|
|
41
|
+
form: bullet-list
|
|
42
|
+
required: yes
|
|
43
|
+
heading: "## Context"
|
|
44
|
+
note: what the decision cannot violate; what chooses between options is decision_drivers
|
|
45
|
+
|
|
46
|
+
- key: decision_drivers
|
|
47
|
+
form: bullet-list
|
|
48
|
+
required: yes
|
|
49
|
+
heading: "## Context"
|
|
50
|
+
note: the criteria that decide the choice
|
|
51
|
+
|
|
52
|
+
- key: decision
|
|
53
|
+
form: scalar
|
|
54
|
+
required: yes
|
|
55
|
+
heading: "## Decision"
|
|
56
|
+
note: the choice alone; its boundary is scope and its reason is decision_drivers
|
|
57
|
+
|
|
58
|
+
- key: scope
|
|
59
|
+
form: bullet-list
|
|
60
|
+
required: yes
|
|
61
|
+
heading: "## Decision"
|
|
62
|
+
note: what the decision covers, and one line opening "excludes:" for what it does not
|
|
63
|
+
|
|
64
|
+
- key: rules
|
|
65
|
+
form: numbered-list
|
|
66
|
+
required: yes
|
|
67
|
+
heading: "## Decision"
|
|
68
|
+
note: the obligations the decision puts on the codebase
|
|
69
|
+
|
|
70
|
+
- key: out_of_scope
|
|
71
|
+
form: record-list
|
|
72
|
+
required: no
|
|
73
|
+
heading: "## Decision"
|
|
74
|
+
record_keys: item, trigger
|
|
75
|
+
note: a deferred item and the event that would reopen it
|
|
76
|
+
|
|
77
|
+
- key: increment_order
|
|
78
|
+
form: numbered-list
|
|
79
|
+
required: no
|
|
80
|
+
heading: "## Decision"
|
|
81
|
+
note: for a decision that ships in stages
|
|
82
|
+
|
|
83
|
+
- key: alternatives
|
|
84
|
+
form: record-list
|
|
85
|
+
required: yes
|
|
86
|
+
heading: "## Alternatives considered"
|
|
87
|
+
record_keys: id, description, rejected_because
|
|
88
|
+
optional_record_keys: kept_as
|
|
89
|
+
note: rejected_because is the cost of the road not taken, so it is not restated in negative
|
|
90
|
+
|
|
91
|
+
- key: positive
|
|
92
|
+
form: bullet-list
|
|
93
|
+
required: yes
|
|
94
|
+
heading: "## Consequences"
|
|
95
|
+
note: what improves
|
|
96
|
+
|
|
97
|
+
- key: negative
|
|
98
|
+
form: bullet-list
|
|
99
|
+
required: yes
|
|
100
|
+
heading: "## Consequences"
|
|
101
|
+
note: a cost the decision is known to incur; something that may not happen is a risk
|
|
102
|
+
|
|
103
|
+
- key: risks
|
|
104
|
+
form: bullet-list
|
|
105
|
+
required: yes
|
|
106
|
+
heading: "## Consequences"
|
|
107
|
+
note: risk -> mitigation, or "not eliminated"
|
|
108
|
+
|
|
109
|
+
- key: unproven_hypothesis
|
|
110
|
+
form: keyed-block
|
|
111
|
+
required: no
|
|
112
|
+
heading: "## Consequences"
|
|
113
|
+
record_keys: claim, acceptance_test, metric
|
|
114
|
+
note: a claim the decision rests on and how it would be measured
|
|
115
|
+
|
|
116
|
+
- key: blast_radius
|
|
117
|
+
form: bullet-list
|
|
118
|
+
required: no
|
|
119
|
+
heading: "## Consequences"
|
|
120
|
+
note: required when the decision changes code; what the change touches
|
|
121
|
+
|
|
122
|
+
custom_sections:
|
|
123
|
+
- none: an ADR block that is not listed above is prose looking for a home
|
|
124
|
+
|
|
125
|
+
enforcement:
|
|
126
|
+
- `npx cnlp check`, over the files a `cnlp.config.json` corpus entry maps to this profile
|
|
127
|
+
- a warning while the ADR is `proposed`, `draft` or `superseded`, an error once it is `accepted` or `active`
|
|
128
|
+
- the gate is where the document becomes a rule other work is measured against
|
|
129
|
+
- no status reaches the checker, so a project gating on lifecycle status applies that severity policy in its own validator
|