openplanr 3.5.0 → 3.6.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/CHANGELOG.md +31 -0
- package/dist/cli/commands/backlog-commands.d.ts +3 -0
- package/dist/cli/commands/backlog-commands.d.ts.map +1 -0
- package/dist/cli/commands/backlog-commands.js +60 -0
- package/dist/cli/commands/backlog-commands.js.map +1 -0
- package/dist/cli/commands/company.d.ts.map +1 -1
- package/dist/cli/commands/company.js +17 -0
- package/dist/cli/commands/company.js.map +1 -1
- package/dist/cli/commands/planning-artifacts.d.ts.map +1 -1
- package/dist/cli/commands/planning-artifacts.js +13 -5
- package/dist/cli/commands/planning-artifacts.js.map +1 -1
- package/dist/dashboard/.vite/manifest.json +1 -1
- package/dist/dashboard/assets/{index-B-l_941W.js → index-C_OquZVk.js} +2 -2
- package/dist/dashboard/assets/{index-B-l_941W.js.map → index-C_OquZVk.js.map} +1 -1
- package/dist/dashboard/dashboard-manifest.json +8 -8
- package/dist/dashboard/index.html +1 -1
- package/dist/services/artifact-service.d.ts.map +1 -1
- package/dist/services/artifact-service.js +13 -6
- package/dist/services/artifact-service.js.map +1 -1
- package/dist/services/company-export-service.d.ts +41 -0
- package/dist/services/company-export-service.d.ts.map +1 -0
- package/dist/services/company-export-service.js +429 -0
- package/dist/services/company-export-service.js.map +1 -0
- package/dist/services/company-sync-service.d.ts +24 -1
- package/dist/services/company-sync-service.d.ts.map +1 -1
- package/dist/services/company-sync-service.js +36 -19
- package/dist/services/company-sync-service.js.map +1 -1
- package/docs/CLI.md +97 -1
- package/lib/backlog-item.d.mts +45 -1
- package/lib/backlog-item.mjs +302 -34
- package/lib/host-packages/adapter-registry.json +4 -4
- package/lib/host-packages/capability-map.json +1 -1
- package/lib/host-packages/claude/.claude-plugin/marketplace.json +2 -2
- package/lib/host-packages/claude/openplanr/.claude-plugin/icon.png +0 -0
- package/lib/host-packages/claude/openplanr/.claude-plugin/plugin.json +1 -1
- package/lib/host-packages/claude/openplanr/.openplanr-content.json +77 -77
- package/lib/host-packages/claude/openplanr/README.md +1 -1
- package/lib/host-packages/claude/openplanr/skills/ceo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/claude/openplanr/skills/ceo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/claude/openplanr/skills/chair-review/SKILL.md +2 -7
- package/lib/host-packages/claude/openplanr/skills/chair-review/references/note-checklist.md +131 -0
- package/lib/host-packages/claude/openplanr/skills/challenger-review/SKILL.md +3 -7
- package/lib/host-packages/claude/openplanr/skills/challenger-review/references/note-checklist.md +131 -0
- package/lib/host-packages/claude/openplanr/skills/cmo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/claude/openplanr/skills/cmo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/claude/openplanr/skills/coo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/claude/openplanr/skills/coo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/claude/openplanr/skills/cpo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/claude/openplanr/skills/cpo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/claude/openplanr/skills/cto-review/references/note-checklist.md +131 -0
- package/lib/host-packages/claude/openplanr/skills/cto-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/claude/openplanr/skills/investigate/SKILL.md +2 -3
- package/lib/host-packages/claude/openplanr/skills/investigate/references/backlog-filing.md +102 -0
- package/lib/host-packages/claude/openplanr/skills/investigate/references/findings.md +8 -16
- package/lib/host-packages/claude/openplanr/skills/operate/SKILL.md +73 -84
- package/lib/host-packages/claude/openplanr/skills/operate/references/note-checklist.md +131 -0
- package/lib/host-packages/claude/openplanr/skills/operate/references/return-contract.md +10 -0
- package/lib/host-packages/claude/openplanr/skills/plan/SKILL.md +16 -17
- package/lib/host-packages/claude/openplanr/skills/plan/references/planning-ids.md +53 -0
- package/lib/host-packages/claude/openplanr/skills/plan/references/return-contract.md +10 -0
- package/lib/host-packages/claude/openplanr/skills/plan-review/SKILL.md +6 -6
- package/lib/host-packages/claude/openplanr/skills/plan-review/references/return-contract.md +10 -0
- package/lib/host-packages/claude/openplanr/skills/release/SKILL.md +40 -139
- package/lib/host-packages/claude/openplanr/skills/release/references/cadence.md +10 -0
- package/lib/host-packages/claude/openplanr/skills/release/references/release-notes.md +1 -1
- package/lib/host-packages/claude/openplanr/skills/release/references/return-contract.md +10 -0
- package/lib/host-packages/claude/openplanr/skills/release/references/schemes.md +4 -2
- package/lib/host-packages/claude/openplanr/skills/ship/SKILL.md +37 -24
- package/lib/host-packages/claude/openplanr/skills/ship/references/backlog-filing.md +102 -0
- package/lib/host-packages/claude/openplanr/skills/ship/references/findings.md +8 -16
- package/lib/host-packages/claude/openplanr/skills/ship/references/return-contract.md +10 -0
- package/lib/host-packages/claude/openplanr/skills/spec/SKILL.md +9 -9
- package/lib/host-packages/claude/openplanr/skills/spec/references/return-contract.md +10 -0
- package/lib/host-packages/cursor/openplanr/.openplanr-content.json +74 -74
- package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-ceo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-ceo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/cursor/openplanr/rules/planr-chair-review/references/note-checklist.md +131 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-chair-review.mdc +2 -7
- package/lib/host-packages/cursor/openplanr/rules/planr-challenger-review/references/note-checklist.md +131 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-challenger-review.mdc +3 -7
- package/lib/host-packages/cursor/openplanr/rules/planr-cmo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-cmo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/cursor/openplanr/rules/planr-coo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-coo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/cursor/openplanr/rules/planr-cpo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-cpo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/cursor/openplanr/rules/planr-cto-review/references/note-checklist.md +131 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-cto-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/cursor/openplanr/rules/planr-investigate/references/backlog-filing.md +102 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-investigate/references/findings.md +8 -16
- package/lib/host-packages/cursor/openplanr/rules/planr-investigate.mdc +2 -3
- package/lib/host-packages/cursor/openplanr/rules/planr-operate/references/note-checklist.md +131 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-operate/references/return-contract.md +10 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-operate.mdc +73 -84
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/references/planning-ids.md +53 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/references/return-contract.md +10 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan-review/references/return-contract.md +10 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan-review.mdc +6 -6
- package/lib/host-packages/cursor/openplanr/rules/planr-plan.mdc +16 -17
- package/lib/host-packages/cursor/openplanr/rules/planr-release/references/cadence.md +10 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-release/references/release-notes.md +1 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-release/references/return-contract.md +10 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-release/references/schemes.md +4 -2
- package/lib/host-packages/cursor/openplanr/rules/planr-release.mdc +40 -139
- package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/backlog-filing.md +102 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/findings.md +8 -16
- package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/return-contract.md +10 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-ship.mdc +37 -24
- package/lib/host-packages/cursor/openplanr/rules/planr-spec/references/return-contract.md +10 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-spec.mdc +9 -9
- package/lib/host-packages/openai/.claude-plugin/marketplace.json +2 -2
- package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +1 -1
- package/lib/host-packages/openai/openplanr/.openplanr-content.json +89 -89
- package/lib/host-packages/openai/openplanr/skills/ceo-review/openplanr.skill.json +4 -4
- package/lib/host-packages/openai/openplanr/skills/ceo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/openai/openplanr/skills/ceo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/openai/openplanr/skills/chair-review/SKILL.md +2 -7
- package/lib/host-packages/openai/openplanr/skills/chair-review/openplanr.skill.json +4 -4
- package/lib/host-packages/openai/openplanr/skills/chair-review/references/note-checklist.md +131 -0
- package/lib/host-packages/openai/openplanr/skills/challenger-review/SKILL.md +3 -7
- package/lib/host-packages/openai/openplanr/skills/challenger-review/openplanr.skill.json +4 -4
- package/lib/host-packages/openai/openplanr/skills/challenger-review/references/note-checklist.md +131 -0
- package/lib/host-packages/openai/openplanr/skills/cmo-review/openplanr.skill.json +4 -4
- package/lib/host-packages/openai/openplanr/skills/cmo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/openai/openplanr/skills/cmo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/openai/openplanr/skills/coo-review/openplanr.skill.json +4 -4
- package/lib/host-packages/openai/openplanr/skills/coo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/openai/openplanr/skills/coo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/openai/openplanr/skills/cpo-review/openplanr.skill.json +4 -4
- package/lib/host-packages/openai/openplanr/skills/cpo-review/references/note-checklist.md +131 -0
- package/lib/host-packages/openai/openplanr/skills/cpo-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/openai/openplanr/skills/cto-review/openplanr.skill.json +4 -4
- package/lib/host-packages/openai/openplanr/skills/cto-review/references/note-checklist.md +131 -0
- package/lib/host-packages/openai/openplanr/skills/cto-review/references/operate-advisor-contract.md +2 -7
- package/lib/host-packages/openai/openplanr/skills/investigate/SKILL.md +2 -3
- package/lib/host-packages/openai/openplanr/skills/investigate/openplanr.skill.json +4 -4
- package/lib/host-packages/openai/openplanr/skills/investigate/references/backlog-filing.md +102 -0
- package/lib/host-packages/openai/openplanr/skills/investigate/references/findings.md +8 -16
- package/lib/host-packages/openai/openplanr/skills/operate/SKILL.md +73 -84
- package/lib/host-packages/openai/openplanr/skills/operate/openplanr.skill.json +15 -4
- package/lib/host-packages/openai/openplanr/skills/operate/references/note-checklist.md +131 -0
- package/lib/host-packages/openai/openplanr/skills/operate/references/return-contract.md +10 -0
- package/lib/host-packages/openai/openplanr/skills/plan/SKILL.md +16 -17
- package/lib/host-packages/openai/openplanr/skills/plan/openplanr.skill.json +15 -4
- package/lib/host-packages/openai/openplanr/skills/plan/references/planning-ids.md +53 -0
- package/lib/host-packages/openai/openplanr/skills/plan/references/return-contract.md +10 -0
- package/lib/host-packages/openai/openplanr/skills/plan-review/SKILL.md +6 -6
- package/lib/host-packages/openai/openplanr/skills/plan-review/openplanr.skill.json +12 -1
- package/lib/host-packages/openai/openplanr/skills/plan-review/references/return-contract.md +10 -0
- package/lib/host-packages/openai/openplanr/skills/release/SKILL.md +40 -139
- package/lib/host-packages/openai/openplanr/skills/release/openplanr.skill.json +4 -4
- package/lib/host-packages/openai/openplanr/skills/release/references/cadence.md +10 -0
- package/lib/host-packages/openai/openplanr/skills/release/references/release-notes.md +1 -1
- package/lib/host-packages/openai/openplanr/skills/release/references/return-contract.md +10 -0
- package/lib/host-packages/openai/openplanr/skills/release/references/schemes.md +4 -2
- package/lib/host-packages/openai/openplanr/skills/ship/SKILL.md +37 -24
- package/lib/host-packages/openai/openplanr/skills/ship/openplanr.skill.json +9 -9
- package/lib/host-packages/openai/openplanr/skills/ship/references/backlog-filing.md +102 -0
- package/lib/host-packages/openai/openplanr/skills/ship/references/findings.md +8 -16
- package/lib/host-packages/openai/openplanr/skills/ship/references/return-contract.md +10 -0
- package/lib/host-packages/openai/openplanr/skills/spec/SKILL.md +9 -9
- package/lib/host-packages/openai/openplanr/skills/spec/openplanr.skill.json +12 -1
- package/lib/host-packages/openai/openplanr/skills/spec/references/return-contract.md +10 -0
- package/package.json +1 -1
- package/lib/host-packages/claude/openplanr/.claude-plugin/icon.svg +0 -11
- package/lib/host-packages/claude/openplanr/runtime/operate/scripts/errors.mjs +0 -8
- package/lib/host-packages/claude/openplanr/runtime/operate/scripts/operate-review-contract.mjs +0 -428
- package/lib/host-packages/claude/openplanr/runtime/operate/scripts/operate-review-note.mjs +0 -686
- package/lib/host-packages/claude/openplanr/runtime/operate/scripts/validate-note.mjs +0 -39
- package/lib/host-packages/claude/openplanr/skills/ceo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/claude/openplanr/skills/chair-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/claude/openplanr/skills/challenger-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/claude/openplanr/skills/cmo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/claude/openplanr/skills/coo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/claude/openplanr/skills/cpo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/claude/openplanr/skills/cto-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/claude/openplanr/skills/investigate/scripts/backlog-item.mjs +0 -339
- package/lib/host-packages/claude/openplanr/skills/operate/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/planning-ids.mjs +0 -166
- package/lib/host-packages/claude/openplanr/skills/release/scripts/collect_changes.sh +0 -85
- package/lib/host-packages/claude/openplanr/skills/ship/scripts/backlog-item.mjs +0 -339
- package/lib/host-packages/claude/openplanr/skills/ship/scripts/discover-verification.mjs +0 -108
- package/lib/host-packages/cursor/openplanr/rules/planr-ceo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/cursor/openplanr/rules/planr-chair-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/cursor/openplanr/rules/planr-challenger-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/cursor/openplanr/rules/planr-cmo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/cursor/openplanr/rules/planr-coo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/cursor/openplanr/rules/planr-cpo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/cursor/openplanr/rules/planr-cto-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/cursor/openplanr/rules/planr-investigate/scripts/backlog-item.mjs +0 -339
- package/lib/host-packages/cursor/openplanr/rules/planr-operate/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/planning-ids.mjs +0 -166
- package/lib/host-packages/cursor/openplanr/rules/planr-release/scripts/collect_changes.sh +0 -85
- package/lib/host-packages/cursor/openplanr/rules/planr-ship/scripts/backlog-item.mjs +0 -339
- package/lib/host-packages/cursor/openplanr/rules/planr-ship/scripts/discover-verification.mjs +0 -108
- package/lib/host-packages/cursor/openplanr/runtime/operate/scripts/errors.mjs +0 -8
- package/lib/host-packages/cursor/openplanr/runtime/operate/scripts/operate-review-contract.mjs +0 -428
- package/lib/host-packages/cursor/openplanr/runtime/operate/scripts/operate-review-note.mjs +0 -686
- package/lib/host-packages/cursor/openplanr/runtime/operate/scripts/validate-note.mjs +0 -39
- package/lib/host-packages/openai/openplanr/runtime/operate/scripts/errors.mjs +0 -8
- package/lib/host-packages/openai/openplanr/runtime/operate/scripts/operate-review-contract.mjs +0 -428
- package/lib/host-packages/openai/openplanr/runtime/operate/scripts/operate-review-note.mjs +0 -686
- package/lib/host-packages/openai/openplanr/runtime/operate/scripts/validate-note.mjs +0 -39
- package/lib/host-packages/openai/openplanr/skills/ceo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/openai/openplanr/skills/chair-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/openai/openplanr/skills/challenger-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/openai/openplanr/skills/cmo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/openai/openplanr/skills/coo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/openai/openplanr/skills/cpo-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/openai/openplanr/skills/cto-review/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/openai/openplanr/skills/investigate/scripts/backlog-item.mjs +0 -339
- package/lib/host-packages/openai/openplanr/skills/operate/scripts/validate-note.mjs +0 -12
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/planning-ids.mjs +0 -166
- package/lib/host-packages/openai/openplanr/skills/release/scripts/collect_changes.sh +0 -85
- package/lib/host-packages/openai/openplanr/skills/ship/scripts/backlog-item.mjs +0 -339
- package/lib/host-packages/openai/openplanr/skills/ship/scripts/discover-verification.mjs +0 -108
package/lib/backlog-item.mjs
CHANGED
|
@@ -84,6 +84,56 @@ class BacklogInputError extends Error {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
const WHEN_PATTERN =
|
|
88
|
+
/^(?:(?:next-unit|trigger|declined):\S(?:[^\r\n]*\S)?|sprint:(?:next|SPRINT-\d{3,}))(?![\s\S])/u;
|
|
89
|
+
const FILE_PATTERN =
|
|
90
|
+
/^(?!\/)(?![A-Za-z]:)(?!.*\\)(?!.*(?:^|\/)\.{1,2}(?:\/|$))(?!.*\/\/)(?!.*:\d+(?::\d+|-\d+)?$)[^\r\n]+$/u;
|
|
91
|
+
|
|
92
|
+
/** @type {typeof import('./backlog-item.d.mts').parseWhen} */
|
|
93
|
+
export function parseWhen(value) {
|
|
94
|
+
if (typeof value !== 'string' || !WHEN_PATTERN.test(value))
|
|
95
|
+
throw new BacklogInputError(
|
|
96
|
+
`Backlog when ${JSON.stringify(value)} is not valid; use next-unit:<ref>, sprint:<SPRINT-NNN or next>, trigger:<event>, or declined:<reason>. Do not store now.`,
|
|
97
|
+
);
|
|
98
|
+
const separator = value.indexOf(':');
|
|
99
|
+
return {
|
|
100
|
+
kind: /** @type {import('./backlog-item.d.mts').BacklogWhen['kind']} */ (
|
|
101
|
+
value.slice(0, separator)
|
|
102
|
+
),
|
|
103
|
+
value: value.slice(separator + 1),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @param {unknown} value */
|
|
108
|
+
function normalizeFilePath(value) {
|
|
109
|
+
return typeof value === 'string'
|
|
110
|
+
? value
|
|
111
|
+
.trim()
|
|
112
|
+
.replaceAll('\\', '/')
|
|
113
|
+
.replace(/^(?:\.\/)+/u, '')
|
|
114
|
+
: '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** @type {typeof import('./backlog-item.d.mts').normalizeBacklogFiles} */
|
|
118
|
+
export function normalizeBacklogFiles(value) {
|
|
119
|
+
if (!Array.isArray(value))
|
|
120
|
+
throw new BacklogInputError(
|
|
121
|
+
`Backlog files ${JSON.stringify(value)} must be an array of repo-relative paths.`,
|
|
122
|
+
);
|
|
123
|
+
return [
|
|
124
|
+
...new Set(
|
|
125
|
+
value.map((item) => {
|
|
126
|
+
const normalized = normalizeFilePath(item);
|
|
127
|
+
if (!FILE_PATTERN.test(normalized))
|
|
128
|
+
throw new BacklogInputError(
|
|
129
|
+
`Backlog files entry ${JSON.stringify(item)} must be a repo-relative path without .. segments, absolute paths, or line numbers.`,
|
|
130
|
+
);
|
|
131
|
+
return normalized;
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
|
|
87
137
|
/** @type {typeof import('./backlog-item.d.mts').backlogFields} */
|
|
88
138
|
export function backlogFields(input) {
|
|
89
139
|
const title = String(input.title ?? input.name ?? '').trim();
|
|
@@ -96,6 +146,12 @@ export function backlogFields(input) {
|
|
|
96
146
|
const criteria = optionalText(input.acceptanceCriteria);
|
|
97
147
|
const notes = optionalText(input.notes);
|
|
98
148
|
const epicId = optionalText(input.epicId);
|
|
149
|
+
if (input.when !== undefined) parseWhen(input.when);
|
|
150
|
+
const files = input.files === undefined ? undefined : normalizeBacklogFiles(input.files);
|
|
151
|
+
if (input.unverified !== undefined && input.unverified !== true)
|
|
152
|
+
throw new BacklogInputError(
|
|
153
|
+
`Backlog unverified ${JSON.stringify(input.unverified)} must be true when present.`,
|
|
154
|
+
);
|
|
99
155
|
return {
|
|
100
156
|
title,
|
|
101
157
|
priority,
|
|
@@ -105,6 +161,9 @@ export function backlogFields(input) {
|
|
|
105
161
|
? {}
|
|
106
162
|
: { acceptanceCriteria: Array.isArray(criteria) ? criteria.map(String) : String(criteria) }),
|
|
107
163
|
...(notes === undefined ? {} : { notes: String(notes) }),
|
|
164
|
+
...(input.when === undefined ? {} : { when: String(input.when) }),
|
|
165
|
+
...(files === undefined ? {} : { files }),
|
|
166
|
+
...(input.unverified === undefined ? {} : { unverified: /** @type {const} */ (true) }),
|
|
108
167
|
...(epicId === undefined ? {} : { epicId: String(epicId) }),
|
|
109
168
|
};
|
|
110
169
|
}
|
|
@@ -121,6 +180,11 @@ export function renderBacklogItem(id, fields, date) {
|
|
|
121
180
|
'status: "open"',
|
|
122
181
|
`created: ${quote(date)}`,
|
|
123
182
|
`updated: ${quote(date)}`,
|
|
183
|
+
...(fields.when === undefined ? [] : [`when: ${quote(fields.when)}`]),
|
|
184
|
+
...(fields.files === undefined
|
|
185
|
+
? []
|
|
186
|
+
: [`files: [${fields.files.map((file) => quote(file)).join(', ')}]`]),
|
|
187
|
+
...(fields.unverified === undefined ? [] : ['unverified: true']),
|
|
124
188
|
...(fields.epicId ? [`epicId: ${quote(fields.epicId)}`] : []),
|
|
125
189
|
'---',
|
|
126
190
|
'',
|
|
@@ -205,60 +269,254 @@ export async function createBacklogItem(
|
|
|
205
269
|
return { id, title: fields.title, artifactPath: relativePath(projectDir, filePath) };
|
|
206
270
|
}
|
|
207
271
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
272
|
+
const NON_STRING_SCALAR =
|
|
273
|
+
/^(?:null|true|false|~|[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|[+-]?0[xob][0-9a-f]+|[+-]?\.(?:inf|nan))$/iu;
|
|
274
|
+
|
|
275
|
+
/** @param {string} source @returns {string[]} */
|
|
276
|
+
function flowStrings(source) {
|
|
277
|
+
const values = [];
|
|
278
|
+
let rest = source.slice(1).trimStart();
|
|
279
|
+
while (!rest.startsWith(']')) {
|
|
280
|
+
const token = /^(?:"(?:\\.|[^"\\])*"|'(?:''|[^'])*'|[^\s,[\]{}"'][^,[\]{}]*)/u.exec(rest)?.[0];
|
|
281
|
+
if (!token) throw new Error('expected a quoted or plain string');
|
|
282
|
+
const plain = token.trim();
|
|
283
|
+
if (token.startsWith('"')) values.push(JSON.parse(token));
|
|
284
|
+
else if (token.startsWith("'")) values.push(token.slice(1, -1).replaceAll("''", "'"));
|
|
285
|
+
else {
|
|
286
|
+
if (NON_STRING_SCALAR.test(plain) || /^[!&*]/u.test(plain) || /:\s|\s#/u.test(plain))
|
|
287
|
+
throw new Error(`expected a string, received ${JSON.stringify(plain)}`);
|
|
288
|
+
values.push(plain);
|
|
289
|
+
}
|
|
290
|
+
rest = rest.slice(token.length).trimStart();
|
|
291
|
+
if (rest.startsWith(',')) rest = rest.slice(1).trimStart();
|
|
292
|
+
else if (!rest.startsWith(']')) throw new Error('expected a comma or closing bracket');
|
|
293
|
+
}
|
|
294
|
+
if (!/^\](?:\s+#.*)?$/u.test(rest)) throw new Error('unexpected text after the closing bracket');
|
|
295
|
+
return values;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** @param {string} text */
|
|
299
|
+
function frontmatter(text) {
|
|
214
300
|
const block = /^---\n([\s\S]*?)\n---/u.exec(text.replace(/\r\n/gu, '\n'))?.[1] ?? '';
|
|
215
|
-
/** @type {
|
|
216
|
-
const fields =
|
|
301
|
+
/** @type {Map<string, string>} */
|
|
302
|
+
const fields = new Map();
|
|
303
|
+
const duplicates = new Set();
|
|
304
|
+
let key;
|
|
217
305
|
for (const line of block.split('\n')) {
|
|
218
|
-
const match = /^(\
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
fields
|
|
223
|
-
|
|
306
|
+
const match = /^([^ \t:#]+):[ \t]*(.*)$/u.exec(line);
|
|
307
|
+
if (match) {
|
|
308
|
+
key = match[1];
|
|
309
|
+
if (fields.has(key)) duplicates.add(key);
|
|
310
|
+
fields.set(key, match[2].startsWith('#') ? '' : match[2]);
|
|
311
|
+
} else if (key && line.trim() && !line.trimStart().startsWith('#')) {
|
|
312
|
+
fields.set(key, `${fields.get(key)}\n${line}`);
|
|
224
313
|
}
|
|
314
|
+
}
|
|
315
|
+
return { fields, duplicates };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** @param {string} source */
|
|
319
|
+
function readString(source) {
|
|
320
|
+
if (/^["']/u.test(source)) {
|
|
321
|
+
const quoted = /^("(?:\\.|[^"\\])*"|'(?:''|[^'])*')(?:\s+#.*)?$/u.exec(source)?.[1];
|
|
322
|
+
if (quoted === undefined) throw new Error('expected a quoted string');
|
|
323
|
+
const values = flowStrings(`[${quoted}]`);
|
|
324
|
+
if (values.length !== 1) throw new Error('expected one string');
|
|
325
|
+
return values[0];
|
|
326
|
+
}
|
|
327
|
+
const value = source.replace(/\s+#.*$/u, '').trim();
|
|
328
|
+
if (
|
|
329
|
+
!value ||
|
|
330
|
+
NON_STRING_SCALAR.test(value) ||
|
|
331
|
+
/^[![\]{}&*|>@`"'%#]|^[?-]\s|:\s|[\r\n]/u.test(value)
|
|
332
|
+
)
|
|
333
|
+
throw new Error('expected a string');
|
|
334
|
+
return value;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** @param {string} source */
|
|
338
|
+
function readStrings(source) {
|
|
339
|
+
if (source.startsWith('[')) return flowStrings(source);
|
|
340
|
+
return source.split('\n').map((line) => {
|
|
341
|
+
const match = /^\s*-\s+(.+)$/u.exec(line);
|
|
342
|
+
if (!match) throw new Error('expected a flow array or block sequence of strings');
|
|
343
|
+
return readString(match[1].trim());
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** @param {string} source */
|
|
348
|
+
function readFiles(source) {
|
|
349
|
+
const files = readStrings(source);
|
|
350
|
+
if (files.some((file) => !FILE_PATTERN.test(file)) || new Set(files).size !== files.length)
|
|
351
|
+
throw new Error(
|
|
352
|
+
'expected distinct repo-relative paths without traversal, absolute paths, or line numbers',
|
|
353
|
+
);
|
|
354
|
+
return files;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** @param {string} source */
|
|
358
|
+
function readDate(source) {
|
|
359
|
+
const value = readString(source);
|
|
360
|
+
const date = new Date(`${value}T00:00:00.000Z`);
|
|
361
|
+
if (
|
|
362
|
+
!/^\d{4}-\d{2}-\d{2}$/u.test(value) ||
|
|
363
|
+
!Number.isFinite(date.getTime()) ||
|
|
364
|
+
date.toISOString().slice(0, 10) !== value
|
|
365
|
+
)
|
|
366
|
+
throw new Error('expected a calendar date in YYYY-MM-DD form');
|
|
367
|
+
return value;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** @param {string} text @param {string} path @param {import('./backlog-item.d.mts').BacklogWarning[]} warnings @param {{ id: string, title: string }} fallbacks */
|
|
371
|
+
function readBacklogFields(text, path, warnings, fallbacks) {
|
|
372
|
+
const { fields, duplicates } = frontmatter(text);
|
|
373
|
+
const needed = new Set(['id', 'title', 'status', 'when', 'files']);
|
|
374
|
+
let invalid = false;
|
|
375
|
+
/**
|
|
376
|
+
* @template T
|
|
377
|
+
* @param {string} field
|
|
378
|
+
* @param {(source: string) => T} parse
|
|
379
|
+
* @param {boolean} [optional]
|
|
380
|
+
* @param {T} [fallback]
|
|
381
|
+
*/
|
|
382
|
+
const read = (field, parse, optional = false, fallback = undefined) => {
|
|
383
|
+
const source = fields.get(field)?.trim();
|
|
225
384
|
try {
|
|
226
|
-
|
|
385
|
+
if (duplicates.has(field)) throw new Error('duplicate frontmatter key');
|
|
386
|
+
if (source === undefined) {
|
|
387
|
+
if (optional) return undefined;
|
|
388
|
+
throw new Error('required field is missing');
|
|
389
|
+
}
|
|
390
|
+
if ((field === 'when' || field === 'files') && (!source || source.startsWith('#')))
|
|
391
|
+
return undefined;
|
|
392
|
+
return parse(source);
|
|
227
393
|
} catch (error) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
394
|
+
const skipped = needed.has(field) && fallback === undefined;
|
|
395
|
+
invalid ||= skipped;
|
|
396
|
+
const value = source ?? 'missing';
|
|
397
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
398
|
+
warnings.push({
|
|
399
|
+
path,
|
|
400
|
+
field,
|
|
401
|
+
value,
|
|
402
|
+
message: `${path}: ${field} = ${JSON.stringify(value)}: ${reason}${skipped ? '; skipping item' : ''}.`,
|
|
403
|
+
});
|
|
404
|
+
return fallback;
|
|
231
405
|
}
|
|
232
|
-
}
|
|
233
|
-
|
|
406
|
+
};
|
|
407
|
+
const id = read(
|
|
408
|
+
'id',
|
|
409
|
+
(source) => {
|
|
410
|
+
const value = readString(source);
|
|
411
|
+
if (!/^[A-Z]+-\d{3,}$/u.test(value))
|
|
412
|
+
throw new Error('expected an artifact ID such as BL-001');
|
|
413
|
+
return value;
|
|
414
|
+
},
|
|
415
|
+
false,
|
|
416
|
+
fallbacks.id,
|
|
417
|
+
);
|
|
418
|
+
const nonEmptyString = (/** @type {string} */ source) => {
|
|
419
|
+
const value = readString(source);
|
|
420
|
+
if (!value.length) throw new Error('expected a non-empty string');
|
|
421
|
+
return value;
|
|
422
|
+
};
|
|
423
|
+
const title = read(
|
|
424
|
+
'title',
|
|
425
|
+
nonEmptyString,
|
|
426
|
+
false,
|
|
427
|
+
fallbacks.title.trim() ? fallbacks.title : undefined,
|
|
428
|
+
);
|
|
429
|
+
const status = read('status', nonEmptyString, false, 'open');
|
|
430
|
+
const when = read(
|
|
431
|
+
'when',
|
|
432
|
+
(source) => {
|
|
433
|
+
const value = readString(source);
|
|
434
|
+
if (!value.trim()) return undefined;
|
|
435
|
+
parseWhen(value);
|
|
436
|
+
return value;
|
|
437
|
+
},
|
|
438
|
+
true,
|
|
439
|
+
);
|
|
440
|
+
const files = read('files', readFiles, true);
|
|
441
|
+
read('priority', (source) => {
|
|
442
|
+
const value = readString(source);
|
|
443
|
+
if (!PRIORITIES.includes(value)) throw new Error(`expected one of ${PRIORITIES.join(', ')}`);
|
|
444
|
+
return value;
|
|
445
|
+
});
|
|
446
|
+
read('tags', readStrings);
|
|
447
|
+
read('created', readDate);
|
|
448
|
+
read('updated', readDate);
|
|
449
|
+
read('epicId', readString, true);
|
|
450
|
+
read(
|
|
451
|
+
'unverified',
|
|
452
|
+
(source) => {
|
|
453
|
+
if (!/^true(?:\s+#.*)?$/iu.test(source)) throw new Error('expected true');
|
|
454
|
+
return true;
|
|
455
|
+
},
|
|
456
|
+
true,
|
|
457
|
+
);
|
|
458
|
+
if (invalid || id === undefined || title === undefined || status === undefined) return undefined;
|
|
459
|
+
return { id, title, status, when, files };
|
|
234
460
|
}
|
|
235
461
|
|
|
236
|
-
/** @
|
|
237
|
-
|
|
462
|
+
/** @param {string} body */
|
|
463
|
+
function evidenceFiles(body) {
|
|
464
|
+
const paths = [
|
|
465
|
+
...body.matchAll(/(?:^|[\s`(['"])([^\s`()[\]<>"']+?):\d+(?::\d+|-\d+)?(?=$|[\s`),.;\]"'])/gu),
|
|
466
|
+
]
|
|
467
|
+
.map((match) => normalizeFilePath(match[1]))
|
|
468
|
+
.filter((file) => FILE_PATTERN.test(file));
|
|
469
|
+
return [...new Set(paths)];
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** @type {typeof import('./backlog-item.d.mts').listBacklog} */
|
|
473
|
+
export async function listBacklog(projectDir, options = {}) {
|
|
474
|
+
const requestedFiles =
|
|
475
|
+
options.files === undefined ? undefined : normalizeBacklogFiles(options.files);
|
|
238
476
|
const { backlogDir } = await projectBacklog(projectDir);
|
|
239
477
|
let names;
|
|
240
478
|
try {
|
|
241
479
|
names = await readdir(backlogDir);
|
|
242
480
|
} catch (error) {
|
|
243
|
-
if (/** @type {NodeJS.ErrnoException} */ (error).code === 'ENOENT')
|
|
481
|
+
if (/** @type {NodeJS.ErrnoException} */ (error).code === 'ENOENT')
|
|
482
|
+
return { items: [], warnings: [] };
|
|
244
483
|
throw error;
|
|
245
484
|
}
|
|
246
485
|
const items = [];
|
|
486
|
+
/** @type {import('./backlog-item.d.mts').BacklogWarning[]} */
|
|
487
|
+
const warnings = [];
|
|
247
488
|
for (const name of names.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))) {
|
|
248
489
|
const match = /^([A-Z]+-\d{3,})-.+\.md$/u.exec(name);
|
|
249
490
|
if (!match) continue;
|
|
250
491
|
const filePath = join(backlogDir, name);
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
|
|
492
|
+
const text = await readFile(filePath, 'utf8');
|
|
493
|
+
const path = relativePath(projectDir, filePath);
|
|
494
|
+
const fields = readBacklogFields(text, path, warnings, {
|
|
495
|
+
id: match[1],
|
|
496
|
+
title: name.slice(match[1].length + 1, -3).replace(/-/g, ' '),
|
|
497
|
+
});
|
|
498
|
+
if (!fields) continue;
|
|
499
|
+
const { id, title, status, when } = fields;
|
|
500
|
+
if (options.open && CLOSED_STATUSES.has(status)) continue;
|
|
501
|
+
if (options.noWhen && when !== undefined) continue;
|
|
502
|
+
const body = text.replace(/^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/u, '');
|
|
503
|
+
const files = fields.files === undefined ? evidenceFiles(body) : fields.files;
|
|
504
|
+
if (requestedFiles && !requestedFiles.some((file) => files.includes(file))) continue;
|
|
254
505
|
items.push({
|
|
255
|
-
id
|
|
256
|
-
title
|
|
506
|
+
id,
|
|
507
|
+
title,
|
|
257
508
|
status,
|
|
258
|
-
path
|
|
509
|
+
path,
|
|
510
|
+
...(when === undefined ? {} : { when }),
|
|
511
|
+
files,
|
|
259
512
|
});
|
|
260
513
|
}
|
|
261
|
-
return items;
|
|
514
|
+
return { items, warnings };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** @type {typeof import('./backlog-item.d.mts').listOpenBacklog} */
|
|
518
|
+
export function listOpenBacklog(projectDir, options = {}) {
|
|
519
|
+
return listBacklog(projectDir, { ...options, open: true });
|
|
262
520
|
}
|
|
263
521
|
|
|
264
522
|
/** @type {typeof import('./backlog-item.d.mts').takeCliHint} */
|
|
@@ -292,17 +550,25 @@ async function readInput(source) {
|
|
|
292
550
|
}
|
|
293
551
|
|
|
294
552
|
const USAGE =
|
|
295
|
-
'Usage: backlog-item.mjs add --project <dir> --data <file|->\n backlog-item.mjs list --project <dir> --open\n';
|
|
553
|
+
'Usage: backlog-item.mjs add --project <dir> --data <file|->\n backlog-item.mjs list --project <dir> --open [--files <paths...>] [--no-when]\n';
|
|
296
554
|
|
|
297
555
|
/** @param {string[]} argv */
|
|
298
556
|
async function main(argv) {
|
|
299
557
|
const [command, ...rest] = argv;
|
|
300
|
-
/** @type {Record<string, string | boolean>} */
|
|
558
|
+
/** @type {Record<string, string | boolean | string[]>} */
|
|
301
559
|
const options = {};
|
|
302
560
|
for (let index = 0; index < rest.length; index += 1) {
|
|
303
561
|
const flag = rest[index];
|
|
304
562
|
if (flag === '--open') options.open = true;
|
|
305
|
-
else if (flag === '--
|
|
563
|
+
else if (flag === '--no-when') options.noWhen = true;
|
|
564
|
+
else if (flag === '--files') {
|
|
565
|
+
const paths = [];
|
|
566
|
+
while (index + 1 < rest.length && !rest[index + 1].startsWith('--'))
|
|
567
|
+
paths.push(rest[++index]);
|
|
568
|
+
if (paths.length === 0)
|
|
569
|
+
throw new Error(`--files needs at least one repo-relative path.\n${USAGE}`);
|
|
570
|
+
options.files = paths;
|
|
571
|
+
} else if (flag === '--project' || flag === '--data')
|
|
306
572
|
options[flag.slice(2)] = rest[++index] ?? '';
|
|
307
573
|
else throw new Error(`Unknown argument: ${flag}\n${USAGE}`);
|
|
308
574
|
}
|
|
@@ -325,7 +591,9 @@ async function main(argv) {
|
|
|
325
591
|
return;
|
|
326
592
|
}
|
|
327
593
|
if (command === 'list' && options.open) {
|
|
328
|
-
process.stdout.write(
|
|
594
|
+
process.stdout.write(
|
|
595
|
+
`${JSON.stringify(await listOpenBacklog(projectDir, { files: Array.isArray(options.files) ? options.files : undefined, noWhen: options.noWhen === true }))}\n`,
|
|
596
|
+
);
|
|
329
597
|
return;
|
|
330
598
|
}
|
|
331
599
|
throw new Error(USAGE);
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"schemaVersion": "1.0.0",
|
|
4
4
|
"protocolVersion": "1.8.0",
|
|
5
5
|
"pipelineVersion": "0.63.0",
|
|
6
|
-
"pluginVersion": "3.
|
|
6
|
+
"pluginVersion": "3.6.0",
|
|
7
7
|
"adapters": [
|
|
8
8
|
{
|
|
9
9
|
"id": "claude-code",
|
|
10
|
-
"version": "3.
|
|
10
|
+
"version": "3.6.0",
|
|
11
11
|
"capabilityLevel": "product",
|
|
12
12
|
"installScopes": [
|
|
13
13
|
"user",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"id": "codex",
|
|
22
|
-
"version": "3.
|
|
22
|
+
"version": "3.6.0",
|
|
23
23
|
"capabilityLevel": "product",
|
|
24
24
|
"installScopes": [
|
|
25
25
|
"user",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"id": "cursor",
|
|
34
|
-
"version": "3.
|
|
34
|
+
"version": "3.6.0",
|
|
35
35
|
"capabilityLevel": "workflow",
|
|
36
36
|
"installScopes": [
|
|
37
37
|
"project"
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
"name": "OpenPlanr"
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
|
-
"version": "3.
|
|
8
|
+
"version": "3.6.0",
|
|
9
9
|
"description": "Generated local OpenPlanr package."
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "planr",
|
|
14
14
|
"source": "./openplanr",
|
|
15
|
-
"version": "3.
|
|
15
|
+
"version": "3.6.0",
|
|
16
16
|
"description": "Close the loop from intent to delivery. Plans as files in your repo, checks before you merge, and a record of what shipped, in Claude Code, Codex and Cursor.",
|
|
17
17
|
"strict": true
|
|
18
18
|
}
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "planr",
|
|
3
3
|
"displayName": "OpenPlanr",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.6.0",
|
|
5
5
|
"description": "Close the loop from intent to delivery. Plans as files in your repo, checks before you merge, and a record of what shipped, in Claude Code, Codex and Cursor.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "OpenPlanr",
|