@planu/cli 0.80.1 → 0.81.1
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/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/config/ac-gap-keywords.json +144 -0
- package/dist/config/challenge-scenarios.json +114 -0
- package/dist/config/code-quality-thresholds.json +114 -0
- package/dist/config/compliance-frameworks.json +150 -0
- package/dist/config/dor-dod-items.json +214 -0
- package/dist/config/drift-severity.json +22 -0
- package/dist/config/elicitation-questions.json +70 -0
- package/dist/config/feasibility-rules.json +91 -0
- package/dist/config/rbac-roles.json +27 -0
- package/dist/config/readiness-config.json +18 -0
- package/dist/config/spec-types.json +42 -0
- package/dist/engine/ac-gap-detector/constants.d.ts.map +1 -1
- package/dist/engine/ac-gap-detector/constants.js +24 -126
- package/dist/engine/ac-gap-detector/constants.js.map +1 -1
- package/dist/engine/ac-gap-detector/keyword-loader.d.ts +13 -0
- package/dist/engine/ac-gap-detector/keyword-loader.d.ts.map +1 -0
- package/dist/engine/ac-gap-detector/keyword-loader.js +35 -0
- package/dist/engine/ac-gap-detector/keyword-loader.js.map +1 -0
- package/dist/engine/challenge-scenarios-loader.d.ts +46 -0
- package/dist/engine/challenge-scenarios-loader.d.ts.map +1 -0
- package/dist/engine/challenge-scenarios-loader.js +83 -0
- package/dist/engine/challenge-scenarios-loader.js.map +1 -0
- package/dist/engine/code-quality-thresholds-loader.d.ts +27 -0
- package/dist/engine/code-quality-thresholds-loader.d.ts.map +1 -0
- package/dist/engine/code-quality-thresholds-loader.js +88 -0
- package/dist/engine/code-quality-thresholds-loader.js.map +1 -0
- package/dist/engine/compliance-injector.d.ts +46 -0
- package/dist/engine/compliance-injector.d.ts.map +1 -0
- package/dist/engine/compliance-injector.js +121 -0
- package/dist/engine/compliance-injector.js.map +1 -0
- package/dist/engine/dor-dod/dod.d.ts +3 -2
- package/dist/engine/dor-dod/dod.d.ts.map +1 -1
- package/dist/engine/dor-dod/dod.js +38 -73
- package/dist/engine/dor-dod/dod.js.map +1 -1
- package/dist/engine/dor-dod/dor.d.ts +3 -2
- package/dist/engine/dor-dod/dor.d.ts.map +1 -1
- package/dist/engine/dor-dod/dor.js +75 -125
- package/dist/engine/dor-dod/dor.js.map +1 -1
- package/dist/engine/dor-dod/items-loader.d.ts +18 -0
- package/dist/engine/dor-dod/items-loader.d.ts.map +1 -0
- package/dist/engine/dor-dod/items-loader.js +55 -0
- package/dist/engine/dor-dod/items-loader.js.map +1 -0
- package/dist/engine/elicitation/question-generator.d.ts.map +1 -1
- package/dist/engine/elicitation/question-generator.js +74 -63
- package/dist/engine/elicitation/question-generator.js.map +1 -1
- package/dist/engine/feasibility-rules-loader.d.ts +13 -0
- package/dist/engine/feasibility-rules-loader.d.ts.map +1 -0
- package/dist/engine/feasibility-rules-loader.js +39 -0
- package/dist/engine/feasibility-rules-loader.js.map +1 -0
- package/dist/engine/feasibility-validator.d.ts +1 -1
- package/dist/engine/feasibility-validator.d.ts.map +1 -1
- package/dist/engine/feasibility-validator.js +21 -78
- package/dist/engine/feasibility-validator.js.map +1 -1
- package/dist/engine/rbac/roles.d.ts +13 -5
- package/dist/engine/rbac/roles.d.ts.map +1 -1
- package/dist/engine/rbac/roles.js +58 -24
- package/dist/engine/rbac/roles.js.map +1 -1
- package/dist/engine/readiness-checker.d.ts +1 -1
- package/dist/engine/readiness-checker.d.ts.map +1 -1
- package/dist/engine/readiness-checker.js +10 -12
- package/dist/engine/readiness-checker.js.map +1 -1
- package/dist/engine/readiness-config-loader.d.ts +8 -0
- package/dist/engine/readiness-config-loader.d.ts.map +1 -0
- package/dist/engine/readiness-config-loader.js +58 -0
- package/dist/engine/readiness-config-loader.js.map +1 -0
- package/dist/engine/spec-types-loader.d.ts +22 -0
- package/dist/engine/spec-types-loader.d.ts.map +1 -0
- package/dist/engine/spec-types-loader.js +50 -0
- package/dist/engine/spec-types-loader.js.map +1 -0
- package/dist/engine/validator/analyzer.d.ts +1 -0
- package/dist/engine/validator/analyzer.d.ts.map +1 -1
- package/dist/engine/validator/analyzer.js +31 -8
- package/dist/engine/validator/analyzer.js.map +1 -1
- package/dist/types/common/primitives.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/plugin-configs.d.ts +105 -0
- package/dist/types/plugin-configs.d.ts.map +1 -0
- package/dist/types/plugin-configs.js +6 -0
- package/dist/types/plugin-configs.js.map +1 -0
- package/package.json +1 -1
- package/src/config/ac-gap-keywords.json +144 -0
- package/src/config/challenge-scenarios.json +114 -0
- package/src/config/code-quality-thresholds.json +114 -0
- package/src/config/compliance-frameworks.json +150 -0
- package/src/config/dor-dod-items.json +214 -0
- package/src/config/drift-severity.json +22 -0
- package/src/config/elicitation-questions.json +70 -0
- package/src/config/feasibility-rules.json +91 -0
- package/src/config/rbac-roles.json +27 -0
- package/src/config/readiness-config.json +18 -0
- package/src/config/spec-types.json +42 -0
|
@@ -1,72 +1,83 @@
|
|
|
1
1
|
// src/engine/elicitation/question-generator.ts
|
|
2
2
|
// SPEC-163: Generates structured clarification questions for spec creation.
|
|
3
|
+
// SPEC-219: Questions loaded from src/config/elicitation-questions.json via ConfigLoader.
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { ConfigLoader } from '../config-loader.js';
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// SPEC-219: Elicitation question config schema
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
const ElicitationQuestionConfigSchema = z.object({
|
|
10
|
+
id: z.string().describe('Unique question identifier (e.g. users, success, scope)'),
|
|
11
|
+
category: z
|
|
12
|
+
.enum(['scope', 'users', 'data', 'behavior', 'constraints', 'success'])
|
|
13
|
+
.describe('Question category: scope, users, data, behavior, constraints, or success'),
|
|
14
|
+
questionTemplate: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe('Question text. Use {title} as placeholder for the feature title'),
|
|
17
|
+
hint: z.string().optional().describe('Optional guidance hint shown to the user'),
|
|
18
|
+
required: z.boolean().describe('Whether the question must be answered'),
|
|
19
|
+
answerType: z
|
|
20
|
+
.enum(['text', 'choice', 'yes-no'])
|
|
21
|
+
.describe('Answer format: text, choice, or yes-no'),
|
|
22
|
+
choices: z.array(z.string()).optional().describe('Available choices when answerType is "choice"'),
|
|
23
|
+
condition: z
|
|
24
|
+
.enum(['always', 'data', 'api'])
|
|
25
|
+
.describe('When to include this question: always, data (needs DB/store context), or api (needs API context)'),
|
|
26
|
+
conditionKeywords: z
|
|
27
|
+
.array(z.string())
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Keywords in description that trigger conditional inclusion'),
|
|
30
|
+
});
|
|
31
|
+
const questionConfigLoader = new ConfigLoader('elicitation-questions', z.array(ElicitationQuestionConfigSchema));
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a question template against the feature title.
|
|
34
|
+
*/
|
|
35
|
+
function resolveTemplate(template, title) {
|
|
36
|
+
return template.replace(/\{title\}/g, title);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Determine whether a conditional question should be included.
|
|
40
|
+
*/
|
|
41
|
+
function shouldInclude(config, lowerDesc, context) {
|
|
42
|
+
if (config.condition === 'always') {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (config.condition === 'data') {
|
|
46
|
+
if (context?.hasDatabase === true) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
return (config.conditionKeywords ?? []).some((kw) => lowerDesc.includes(kw));
|
|
50
|
+
}
|
|
51
|
+
// condition === 'api' is the only remaining case
|
|
52
|
+
if (context?.isApi === true) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return (config.conditionKeywords ?? []).some((kw) => lowerDesc.includes(kw));
|
|
56
|
+
}
|
|
3
57
|
/** Generate standard clarification questions for a feature spec */
|
|
4
58
|
export function generateSpecQuestions(title, description, context) {
|
|
5
|
-
const
|
|
59
|
+
const configs = questionConfigLoader.load();
|
|
6
60
|
const lowerDesc = description.toLowerCase();
|
|
7
|
-
questions
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
hint
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
question: 'What is the scope of this change?',
|
|
27
|
-
required: true,
|
|
28
|
-
answerType: 'choice',
|
|
29
|
-
choices: [
|
|
30
|
-
'Small change in one place',
|
|
31
|
-
'Medium feature across 2-3 areas',
|
|
32
|
-
'Large cross-module change',
|
|
33
|
-
'Major architectural change',
|
|
34
|
-
],
|
|
35
|
-
});
|
|
36
|
-
questions.push({
|
|
37
|
-
id: 'edge_cases',
|
|
38
|
-
category: 'behavior',
|
|
39
|
-
question: 'What should happen in error or edge cases?',
|
|
40
|
-
hint: 'e.g., "show error message", "redirect to login", "fail silently"',
|
|
41
|
-
required: false,
|
|
42
|
-
answerType: 'text',
|
|
43
|
-
});
|
|
44
|
-
if (context?.hasDatabase === true || lowerDesc.includes('data') || lowerDesc.includes('store')) {
|
|
45
|
-
questions.push({
|
|
46
|
-
id: 'data',
|
|
47
|
-
category: 'data',
|
|
48
|
-
question: 'What data needs to be stored or retrieved for this feature?',
|
|
49
|
-
hint: 'List the key fields/entities involved',
|
|
50
|
-
required: false,
|
|
51
|
-
answerType: 'text',
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
if (context?.isApi === true || lowerDesc.includes('api') || lowerDesc.includes('endpoint')) {
|
|
55
|
-
questions.push({
|
|
56
|
-
id: 'api_contract',
|
|
57
|
-
category: 'constraints',
|
|
58
|
-
question: 'Does this require a new API endpoint or changes to existing ones?',
|
|
59
|
-
required: false,
|
|
60
|
-
answerType: 'yes-no',
|
|
61
|
-
});
|
|
61
|
+
const questions = [];
|
|
62
|
+
for (const config of configs) {
|
|
63
|
+
if (!shouldInclude(config, lowerDesc, context)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const question = {
|
|
67
|
+
id: config.id,
|
|
68
|
+
category: config.category,
|
|
69
|
+
question: resolveTemplate(config.questionTemplate, title),
|
|
70
|
+
required: config.required,
|
|
71
|
+
answerType: config.answerType,
|
|
72
|
+
};
|
|
73
|
+
if (config.hint !== undefined) {
|
|
74
|
+
question.hint = config.hint;
|
|
75
|
+
}
|
|
76
|
+
if (config.choices !== undefined) {
|
|
77
|
+
question.choices = config.choices;
|
|
78
|
+
}
|
|
79
|
+
questions.push(question);
|
|
62
80
|
}
|
|
63
|
-
questions.push({
|
|
64
|
-
id: 'performance',
|
|
65
|
-
category: 'constraints',
|
|
66
|
-
question: 'Are there performance requirements (response time, load, etc.)?',
|
|
67
|
-
required: false,
|
|
68
|
-
answerType: 'text',
|
|
69
|
-
});
|
|
70
81
|
return questions;
|
|
71
82
|
}
|
|
72
83
|
/** Format questions as a readable prompt for the LLM to present to the user */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question-generator.js","sourceRoot":"","sources":["../../../src/engine/elicitation/question-generator.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,4EAA4E;
|
|
1
|
+
{"version":3,"file":"question-generator.js","sourceRoot":"","sources":["../../../src/engine/elicitation/question-generator.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,4EAA4E;AAC5E,0FAA0F;AAE1F,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAInD,8EAA8E;AAC9E,+CAA+C;AAC/C,8EAA8E;AAE9E,MAAM,+BAA+B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yDAAyD,CAAC;IAClF,QAAQ,EAAE,CAAC;SACR,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;SACtE,QAAQ,CAAC,0EAA0E,CAAC;IACvF,gBAAgB,EAAE,CAAC;SAChB,MAAM,EAAE;SACR,QAAQ,CAAC,iEAAiE,CAAC;IAC9E,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;IAChF,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IACvE,UAAU,EAAE,CAAC;SACV,IAAI,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;SAClC,QAAQ,CAAC,wCAAwC,CAAC;IACrD,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;IACjG,SAAS,EAAE,CAAC;SACT,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;SAC/B,QAAQ,CACP,kGAAkG,CACnG;IACH,iBAAiB,EAAE,CAAC;SACjB,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;SACjB,QAAQ,EAAE;SACV,QAAQ,CAAC,4DAA4D,CAAC;CAC1E,CAAC,CAAC;AAEH,MAAM,oBAAoB,GAAG,IAAI,YAAY,CAC3C,uBAAuB,EACvB,CAAC,CAAC,KAAK,CAAC,+BAA+B,CAAC,CACzC,CAAC;AAEF;;GAEG;AACH,SAAS,eAAe,CAAC,QAAgB,EAAE,KAAa;IACtD,OAAO,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;AAC/C,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CACpB,MAAiC,EACjC,SAAiB,EACjB,OAAwE;IAExE,IAAI,MAAM,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,MAAM,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;QAChC,IAAI,OAAO,EAAE,WAAW,KAAK,IAAI,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/E,CAAC;IAED,iDAAiD;IACjD,IAAI,OAAO,EAAE,KAAK,KAAK,IAAI,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,qBAAqB,CACnC,KAAa,EACb,WAAmB,EACnB,OAAwE;IAExE,MAAM,OAAO,GAAG,oBAAoB,CAAC,IAAI,EAAE,CAAC;IAC5C,MAAM,SAAS,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;IAC5C,MAAM,SAAS,GAA0B,EAAE,CAAC;IAE5C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;YAC/C,SAAS;QACX,CAAC;QAED,MAAM,QAAQ,GAAwB;YACpC,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,eAAe,CAAC,MAAM,CAAC,gBAAgB,EAAE,KAAK,CAAC;YACzD,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,UAAU,EAAE,MAAM,CAAC,UAAU;SAC9B,CAAC;QAEF,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,QAAQ,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QAC9B,CAAC;QAED,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACjC,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QACpC,CAAC;QAED,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,uBAAuB,CAAC,OAA2B;IACjE,MAAM,KAAK,GAAa;QACtB,iCAAiC,OAAO,CAAC,KAAK,GAAG;QACjD,EAAE;QACF,uEAAuE;QACvE,EAAE;KACH,CAAC;IAEF,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,eAAe,CAAC;QAChE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC,CAAC;QACrD,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACrC,CAAC;QACD,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC1B,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QACpD,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,KAAK,CAAC,IAAI,CACR,0FAA0F,CAC3F,CAAC;IAEF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAC1B,SAAgC,EAChC,WAAmB;IAEnB,MAAM,OAAO,GAA2B,EAAE,CAAC;IAE3C,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACtC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACrB,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACpD,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;YACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;YACvC,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,GAAG,IAAI,CAAC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACvC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACzC,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FeasibilityRule } from '../types/index.js';
|
|
2
|
+
export type { FeasibilityRule };
|
|
3
|
+
/**
|
|
4
|
+
* Load the merged feasibility keyword rules for a given project.
|
|
5
|
+
* Falls back to system defaults when no overrides exist.
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadFeasibilityRules(projectHash?: string): FeasibilityRule[];
|
|
8
|
+
/**
|
|
9
|
+
* Extract keywords for a specific category from the loaded rules.
|
|
10
|
+
* Returns an empty array if no rule exists for the category.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getKeywordsByCategory(rules: FeasibilityRule[], category: FeasibilityRule['category']): string[];
|
|
13
|
+
//# sourceMappingURL=feasibility-rules-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feasibility-rules-loader.d.ts","sourceRoot":"","sources":["../../src/engine/feasibility-rules-loader.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAkBzD,YAAY,EAAE,eAAe,EAAE,CAAC;AAUhC;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,eAAe,EAAE,CAE5E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,eAAe,EAAE,EACxB,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,GACpC,MAAM,EAAE,CAEV"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// engine/feasibility-rules-loader.ts — Config loader for feasibility-validator (SPEC-213)
|
|
2
|
+
//
|
|
3
|
+
// Provides the 3-layer configurable keyword rules using ConfigLoader.
|
|
4
|
+
// Layer 1: src/config/feasibility-rules.json — system defaults
|
|
5
|
+
// Layer 2: data/global/feasibility-rules-custom.json — global overrides
|
|
6
|
+
// Layer 3: data/projects/{hash}/config/feasibility-rules-overrides.json — project overrides
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { ConfigLoader } from './config-loader.js';
|
|
9
|
+
// ── Schema ────────────────────────────────────────────────────────────────────
|
|
10
|
+
const feasibilityRuleSchema = z.object({
|
|
11
|
+
id: z.string().min(1).describe('Unique identifier for this rule group'),
|
|
12
|
+
category: z
|
|
13
|
+
.enum(['feature', 'performance', 'security', 'cheap', 'offline', 'realtime'])
|
|
14
|
+
.describe('Category of keywords. Valid values: feature, performance, security, cheap, offline, realtime'),
|
|
15
|
+
keywords: z
|
|
16
|
+
.array(z.string().min(1))
|
|
17
|
+
.min(1)
|
|
18
|
+
.describe('List of keyword strings to match in requirements text (case-insensitive)'),
|
|
19
|
+
});
|
|
20
|
+
const feasibilityRulesSchema = z
|
|
21
|
+
.array(feasibilityRuleSchema)
|
|
22
|
+
.describe('List of keyword rule groups for feasibility validation');
|
|
23
|
+
// ── Loader instance ───────────────────────────────────────────────────────────
|
|
24
|
+
const loader = new ConfigLoader('feasibility-rules', feasibilityRulesSchema);
|
|
25
|
+
/**
|
|
26
|
+
* Load the merged feasibility keyword rules for a given project.
|
|
27
|
+
* Falls back to system defaults when no overrides exist.
|
|
28
|
+
*/
|
|
29
|
+
export function loadFeasibilityRules(projectHash) {
|
|
30
|
+
return loader.load(projectHash);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extract keywords for a specific category from the loaded rules.
|
|
34
|
+
* Returns an empty array if no rule exists for the category.
|
|
35
|
+
*/
|
|
36
|
+
export function getKeywordsByCategory(rules, category) {
|
|
37
|
+
return rules.find((r) => r.category === category)?.keywords ?? [];
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=feasibility-rules-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feasibility-rules-loader.js","sourceRoot":"","sources":["../../src/engine/feasibility-rules-loader.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,EAAE;AACF,sEAAsE;AACtE,+DAA+D;AAC/D,wEAAwE;AACxE,4FAA4F;AAE5F,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,iFAAiF;AAEjF,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IACvE,QAAQ,EAAE,CAAC;SACR,IAAI,CAAC,CAAC,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;SAC5E,QAAQ,CACP,8FAA8F,CAC/F;IACH,QAAQ,EAAE,CAAC;SACR,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;SACxB,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,0EAA0E,CAAC;CACxF,CAAC,CAAC;AAIH,MAAM,sBAAsB,GAAG,CAAC;KAC7B,KAAK,CAAC,qBAAqB,CAAC;KAC5B,QAAQ,CAAC,wDAAwD,CAAC,CAAC;AAEtE,iFAAiF;AAEjF,MAAM,MAAM,GAAG,IAAI,YAAY,CAAkB,mBAAmB,EAAE,sBAAsB,CAAC,CAAC;AAE9F;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,WAAoB;IACvD,OAAO,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAwB,EACxB,QAAqC;IAErC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,EAAE,QAAQ,IAAI,EAAE,CAAC;AACpE,CAAC"}
|
|
@@ -3,5 +3,5 @@ import type { ProjectKnowledge, FeasibilityReport } from '../types/index.js';
|
|
|
3
3
|
* Validate whether a set of requirements is feasible given project context.
|
|
4
4
|
* Returns a FeasibilityReport with scored issues and a viability flag.
|
|
5
5
|
*/
|
|
6
|
-
export declare function validateFeasibility(requirements: string[], knowledge: ProjectKnowledge): FeasibilityReport;
|
|
6
|
+
export declare function validateFeasibility(requirements: string[], knowledge: ProjectKnowledge, projectHash?: string): FeasibilityReport;
|
|
7
7
|
//# sourceMappingURL=feasibility-validator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feasibility-validator.d.ts","sourceRoot":"","sources":["../../src/engine/feasibility-validator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"feasibility-validator.d.ts","sourceRoot":"","sources":["../../src/engine/feasibility-validator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAwB/F;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EAAE,EACtB,SAAS,EAAE,gBAAgB,EAC3B,WAAW,CAAC,EAAE,MAAM,GACnB,iBAAiB,CAwCnB"}
|
|
@@ -1,74 +1,10 @@
|
|
|
1
1
|
// engine/feasibility-validator.ts — Requirement feasibility validation (SPEC-040)
|
|
2
2
|
// Pure function — no I/O. Evaluates a list of requirements and returns a FeasibilityReport.
|
|
3
|
+
import { loadFeasibilityRules, getKeywordsByCategory } from './feasibility-rules-loader.js';
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
4
5
|
// Constants
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
6
7
|
const DISCLAIMER = 'Based on patterns from similar projects. Adjust based on your specific context.';
|
|
7
|
-
const FEATURE_KEYWORDS = [
|
|
8
|
-
'quiero',
|
|
9
|
-
'debe',
|
|
10
|
-
'should',
|
|
11
|
-
'feature',
|
|
12
|
-
'screen',
|
|
13
|
-
'page',
|
|
14
|
-
'module',
|
|
15
|
-
'endpoint',
|
|
16
|
-
'flow',
|
|
17
|
-
'integration',
|
|
18
|
-
'dashboard',
|
|
19
|
-
'panel',
|
|
20
|
-
'view',
|
|
21
|
-
'form',
|
|
22
|
-
'report',
|
|
23
|
-
'notification',
|
|
24
|
-
'alert',
|
|
25
|
-
'widget',
|
|
26
|
-
];
|
|
27
|
-
const PERFORMANCE_KEYWORDS = [
|
|
28
|
-
'ultra-fast',
|
|
29
|
-
'ultra fast',
|
|
30
|
-
'< 100ms',
|
|
31
|
-
'<100ms',
|
|
32
|
-
'high performance',
|
|
33
|
-
'low latency',
|
|
34
|
-
'sub-second',
|
|
35
|
-
'instant',
|
|
36
|
-
'real-time response',
|
|
37
|
-
];
|
|
38
|
-
const SECURITY_KEYWORDS = [
|
|
39
|
-
'encrypted',
|
|
40
|
-
'encryption',
|
|
41
|
-
'highly secure',
|
|
42
|
-
'compliance',
|
|
43
|
-
'hipaa',
|
|
44
|
-
'gdpr',
|
|
45
|
-
'pci',
|
|
46
|
-
'soc2',
|
|
47
|
-
'zero-trust',
|
|
48
|
-
'audit log',
|
|
49
|
-
'audit trail',
|
|
50
|
-
];
|
|
51
|
-
const CHEAP_KEYWORDS = [
|
|
52
|
-
'cheap',
|
|
53
|
-
'zero cost',
|
|
54
|
-
'free',
|
|
55
|
-
'no cost',
|
|
56
|
-
'minimal cost',
|
|
57
|
-
'low cost',
|
|
58
|
-
'budget',
|
|
59
|
-
'affordable',
|
|
60
|
-
'open source only',
|
|
61
|
-
];
|
|
62
|
-
const OFFLINE_KEYWORDS = ['offline', 'offline mode', 'no internet', 'works offline'];
|
|
63
|
-
const REALTIME_KEYWORDS = [
|
|
64
|
-
'real-time sync',
|
|
65
|
-
'realtime sync',
|
|
66
|
-
'live sync',
|
|
67
|
-
'instant sync',
|
|
68
|
-
'real-time updates',
|
|
69
|
-
'websocket sync',
|
|
70
|
-
'live updates',
|
|
71
|
-
];
|
|
72
8
|
// ---------------------------------------------------------------------------
|
|
73
9
|
// Score weights
|
|
74
10
|
// ---------------------------------------------------------------------------
|
|
@@ -84,20 +20,27 @@ const SCORE_PENALTIES = {
|
|
|
84
20
|
* Validate whether a set of requirements is feasible given project context.
|
|
85
21
|
* Returns a FeasibilityReport with scored issues and a viability flag.
|
|
86
22
|
*/
|
|
87
|
-
export function validateFeasibility(requirements, knowledge) {
|
|
23
|
+
export function validateFeasibility(requirements, knowledge, projectHash) {
|
|
24
|
+
const rules = loadFeasibilityRules(projectHash);
|
|
25
|
+
const featureKeywords = getKeywordsByCategory(rules, 'feature');
|
|
26
|
+
const performanceKeywords = getKeywordsByCategory(rules, 'performance');
|
|
27
|
+
const securityKeywords = getKeywordsByCategory(rules, 'security');
|
|
28
|
+
const cheapKeywords = getKeywordsByCategory(rules, 'cheap');
|
|
29
|
+
const offlineKeywords = getKeywordsByCategory(rules, 'offline');
|
|
30
|
+
const realtimeKeywords = getKeywordsByCategory(rules, 'realtime');
|
|
88
31
|
const issues = [];
|
|
89
32
|
const contextUsed = [];
|
|
90
33
|
const fullText = requirements.join(' ').toLowerCase();
|
|
91
34
|
const teamSize = resolveTeamSize(knowledge, contextUsed);
|
|
92
|
-
const featureCount = countFeatures(requirements);
|
|
35
|
+
const featureCount = countFeatures(requirements, featureKeywords);
|
|
93
36
|
// Rule 1: Timeline / team size
|
|
94
37
|
checkTimelineIssues(featureCount, teamSize, issues, contextUsed);
|
|
95
38
|
// Rule 2: Performance paradox
|
|
96
|
-
checkPerformanceParadox(fullText, issues);
|
|
39
|
+
checkPerformanceParadox(fullText, performanceKeywords, securityKeywords, cheapKeywords, issues);
|
|
97
40
|
// Rule 3: Scope creep
|
|
98
41
|
checkScopeCreep(requirements.length, issues);
|
|
99
42
|
// Rule 4: Offline vs real-time contradiction
|
|
100
|
-
checkOfflineRealtime(fullText, issues);
|
|
43
|
+
checkOfflineRealtime(fullText, offlineKeywords, realtimeKeywords, issues);
|
|
101
44
|
// Score calculation
|
|
102
45
|
let score = 100;
|
|
103
46
|
for (const issue of issues) {
|
|
@@ -137,10 +80,10 @@ function checkTimelineIssues(featureCount, teamSize, issues, contextUsed) {
|
|
|
137
80
|
});
|
|
138
81
|
}
|
|
139
82
|
}
|
|
140
|
-
function checkPerformanceParadox(fullText, issues) {
|
|
141
|
-
const hasPerf =
|
|
142
|
-
const hasSecurity =
|
|
143
|
-
const hasCheap =
|
|
83
|
+
function checkPerformanceParadox(fullText, performanceKeywords, securityKeywords, cheapKeywords, issues) {
|
|
84
|
+
const hasPerf = performanceKeywords.some((kw) => fullText.includes(kw));
|
|
85
|
+
const hasSecurity = securityKeywords.some((kw) => fullText.includes(kw));
|
|
86
|
+
const hasCheap = cheapKeywords.some((kw) => fullText.includes(kw));
|
|
144
87
|
if (hasPerf && hasSecurity && hasCheap) {
|
|
145
88
|
issues.push({
|
|
146
89
|
type: 'performance',
|
|
@@ -166,9 +109,9 @@ function checkScopeCreep(requirementCount, issues) {
|
|
|
166
109
|
});
|
|
167
110
|
}
|
|
168
111
|
}
|
|
169
|
-
function checkOfflineRealtime(fullText, issues) {
|
|
170
|
-
const hasOffline =
|
|
171
|
-
const hasRealtime =
|
|
112
|
+
function checkOfflineRealtime(fullText, offlineKeywords, realtimeKeywords, issues) {
|
|
113
|
+
const hasOffline = offlineKeywords.some((kw) => fullText.includes(kw));
|
|
114
|
+
const hasRealtime = realtimeKeywords.some((kw) => fullText.includes(kw));
|
|
172
115
|
if (hasOffline && hasRealtime) {
|
|
173
116
|
issues.push({
|
|
174
117
|
type: 'contradiction',
|
|
@@ -193,10 +136,10 @@ function resolveTeamSize(knowledge, contextUsed) {
|
|
|
193
136
|
// We cannot reliably know team size unless contributed — return 0 (unknown)
|
|
194
137
|
return 0;
|
|
195
138
|
}
|
|
196
|
-
function countFeatures(requirements) {
|
|
139
|
+
function countFeatures(requirements, featureKeywords) {
|
|
197
140
|
const text = requirements.join(' ').toLowerCase();
|
|
198
141
|
const words = text.split(/\W+/);
|
|
199
|
-
return words.filter((w) =>
|
|
142
|
+
return words.filter((w) => featureKeywords.includes(w)).length;
|
|
200
143
|
}
|
|
201
144
|
function buildSummary(score, issues, viable) {
|
|
202
145
|
if (issues.length === 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feasibility-validator.js","sourceRoot":"","sources":["../../src/engine/feasibility-validator.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,4FAA4F;
|
|
1
|
+
{"version":3,"file":"feasibility-validator.js","sourceRoot":"","sources":["../../src/engine/feasibility-validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import type { RbacRole, PermissionAction } from '../../types/index.js';
|
|
1
|
+
import type { RbacRole, PermissionAction, RbacRoleConfig } from '../../types/index.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Load all role definitions for an optional project, defaulting to system roles.
|
|
4
|
+
* Returns a map from role id to its config entry.
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadRoleConfigs(projectHash?: string): Map<string, RbacRoleConfig>;
|
|
7
|
+
/**
|
|
8
|
+
* Permissions granted to each role (system defaults).
|
|
4
9
|
* All roles implicitly support 'read' — it is always included.
|
|
10
|
+
*
|
|
11
|
+
* For runtime extensibility (custom roles added via org/project config),
|
|
12
|
+
* use loadRoleConfigs(projectHash) instead.
|
|
5
13
|
*/
|
|
6
14
|
export declare const ROLE_PERMISSIONS: Readonly<Record<RbacRole, ReadonlySet<PermissionAction>>>;
|
|
7
15
|
export declare const ROLE_DESCRIPTIONS: Readonly<Record<RbacRole, string>>;
|
|
8
16
|
/** Returns true if the given role has the requested permission on any resource. */
|
|
9
|
-
export declare function roleHasPermission(role:
|
|
10
|
-
/** Returns all roles defined in the system. */
|
|
11
|
-
export declare function listRoles():
|
|
17
|
+
export declare function roleHasPermission(role: string, action: PermissionAction): boolean;
|
|
18
|
+
/** Returns all roles defined in the system (or project if projectHash provided). */
|
|
19
|
+
export declare function listRoles(projectHash?: string): string[];
|
|
12
20
|
//# sourceMappingURL=roles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../../../src/engine/rbac/roles.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../../../src/engine/rbac/roles.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AA8BvF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAGjF;AASD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAKvB,CAAC;AAMjE,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAE1B,CAAC;AAMxC,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAKjF;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD"}
|
|
@@ -1,37 +1,71 @@
|
|
|
1
|
-
// engine/rbac/roles.ts — Role definitions and permission sets
|
|
1
|
+
// engine/rbac/roles.ts — Role definitions and permission sets (SPEC-166, SPEC-215)
|
|
2
|
+
//
|
|
3
|
+
// Roles and permissions are now loaded from src/config/rbac-roles.json via
|
|
4
|
+
// ConfigLoader, making them extensible per organization without code changes.
|
|
5
|
+
// The public API is backward-compatible: ROLE_PERMISSIONS, ROLE_DESCRIPTIONS,
|
|
6
|
+
// roleHasPermission, and listRoles all work exactly as before.
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { ConfigLoader } from '../config-loader.js';
|
|
2
9
|
// ---------------------------------------------------------------------------
|
|
3
|
-
//
|
|
10
|
+
// Config schema
|
|
4
11
|
// ---------------------------------------------------------------------------
|
|
12
|
+
const PermissionActionSchema = z.enum([
|
|
13
|
+
'create',
|
|
14
|
+
'read',
|
|
15
|
+
'update',
|
|
16
|
+
'delete',
|
|
17
|
+
'approve',
|
|
18
|
+
'implement',
|
|
19
|
+
'configure',
|
|
20
|
+
]);
|
|
21
|
+
const RbacRoleConfigSchema = z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
description: z.string(),
|
|
24
|
+
permissions: z.array(PermissionActionSchema),
|
|
25
|
+
});
|
|
26
|
+
const RbacRoleConfigArraySchema = z.array(RbacRoleConfigSchema);
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Config loading (module-level, synchronous)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const loader = new ConfigLoader('rbac-roles', RbacRoleConfigArraySchema);
|
|
5
31
|
/**
|
|
6
|
-
*
|
|
32
|
+
* Load all role definitions for an optional project, defaulting to system roles.
|
|
33
|
+
* Returns a map from role id to its config entry.
|
|
34
|
+
*/
|
|
35
|
+
export function loadRoleConfigs(projectHash) {
|
|
36
|
+
const items = loader.load(projectHash);
|
|
37
|
+
return new Map(items.map((item) => [item.id, item]));
|
|
38
|
+
}
|
|
39
|
+
// System-level defaults — used for ROLE_PERMISSIONS and ROLE_DESCRIPTIONS constants.
|
|
40
|
+
const _systemRoles = loader.load();
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Role permission matrix (backward-compatible)
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
/**
|
|
45
|
+
* Permissions granted to each role (system defaults).
|
|
7
46
|
* All roles implicitly support 'read' — it is always included.
|
|
47
|
+
*
|
|
48
|
+
* For runtime extensibility (custom roles added via org/project config),
|
|
49
|
+
* use loadRoleConfigs(projectHash) instead.
|
|
8
50
|
*/
|
|
9
|
-
export const ROLE_PERMISSIONS =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Observer: new Set(['read']),
|
|
15
|
-
};
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Role hierarchy (for display / description purposes)
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
export const ROLE_DESCRIPTIONS = {
|
|
20
|
-
Admin: 'Full access to all operations: create, read, update, delete, approve, implement, configure.',
|
|
21
|
-
Architect: 'Can create/approve specs and configure the project constitution.',
|
|
22
|
-
Developer: 'Can read specs and implement (update) them.',
|
|
23
|
-
Reviewer: 'Can read and approve/reject specs.',
|
|
24
|
-
Observer: 'Read-only access to all specs and project data.',
|
|
25
|
-
};
|
|
51
|
+
export const ROLE_PERMISSIONS = Object.freeze(Object.fromEntries(_systemRoles.map((r) => [r.id, new Set(r.permissions)])));
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Role descriptions (backward-compatible)
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
export const ROLE_DESCRIPTIONS = Object.freeze(Object.fromEntries(_systemRoles.map((r) => [r.id, r.description])));
|
|
26
56
|
// ---------------------------------------------------------------------------
|
|
27
57
|
// Helpers
|
|
28
58
|
// ---------------------------------------------------------------------------
|
|
29
59
|
/** Returns true if the given role has the requested permission on any resource. */
|
|
30
60
|
export function roleHasPermission(role, action) {
|
|
31
|
-
|
|
61
|
+
const perms = ROLE_PERMISSIONS[role];
|
|
62
|
+
return perms?.has(action) ?? false;
|
|
32
63
|
}
|
|
33
|
-
/** Returns all roles defined in the system. */
|
|
34
|
-
export function listRoles() {
|
|
35
|
-
|
|
64
|
+
/** Returns all roles defined in the system (or project if projectHash provided). */
|
|
65
|
+
export function listRoles(projectHash) {
|
|
66
|
+
if (projectHash) {
|
|
67
|
+
return [...loadRoleConfigs(projectHash).keys()];
|
|
68
|
+
}
|
|
69
|
+
return _systemRoles.map((r) => r.id);
|
|
36
70
|
}
|
|
37
71
|
//# sourceMappingURL=roles.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roles.js","sourceRoot":"","sources":["../../../src/engine/rbac/roles.ts"],"names":[],"mappings":"AAAA,2EAA2E;
|
|
1
|
+
{"version":3,"file":"roles.js","sourceRoot":"","sources":["../../../src/engine/rbac/roles.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,EAAE;AACF,2EAA2E;AAC3E,8EAA8E;AAC9E,8EAA8E;AAC9E,+DAA+D;AAE/D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC;IACpC,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,WAAW;IACX,WAAW;CACZ,CAAC,CAAC;AAEH,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC;CAC7C,CAAC,CAAC;AAEH,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;AAEhE,8EAA8E;AAC9E,6CAA6C;AAC7C,8EAA8E;AAE9E,MAAM,MAAM,GAAG,IAAI,YAAY,CAAiB,YAAY,EAAE,yBAAyB,CAAC,CAAC;AAEzF;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,WAAoB;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACvC,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,qFAAqF;AACrF,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;AAEnC,8EAA8E;AAC9E,+CAA+C;AAC/C,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,MAAM,CAAC,MAAM,CACX,MAAM,CAAC,WAAW,CAChB,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,WAAW,CAAkC,CAAC,CAAC,CACzF,CAC2D,CAAC;AAEjE,8EAA8E;AAC9E,0CAA0C;AAC1C,8EAA8E;AAE9E,MAAM,CAAC,MAAM,iBAAiB,GAAuC,MAAM,CAAC,MAAM,CAChF,MAAM,CAAC,WAAW,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAC7B,CAAC;AAExC,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,mFAAmF;AACnF,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,MAAwB;IACtE,MAAM,KAAK,GAA8C,gBAAgB,CAAC,IAAgB,CAE7E,CAAC;IACd,OAAO,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC;AACrC,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,SAAS,CAAC,WAAoB;IAC5C,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACvC,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Spec, ReadinessReport } from '../types/index.js';
|
|
2
|
-
export declare function checkSpecReadiness(spec: Spec, mode: 'strict' | 'lenient'): Promise<ReadinessReport>;
|
|
2
|
+
export declare function checkSpecReadiness(spec: Spec, mode: 'strict' | 'lenient', projectHash?: string): Promise<ReadinessReport>;
|
|
3
3
|
//# sourceMappingURL=readiness-checker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readiness-checker.d.ts","sourceRoot":"","sources":["../../src/engine/readiness-checker.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,IAAI,EACJ,eAAe,EAIhB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"readiness-checker.d.ts","sourceRoot":"","sources":["../../src/engine/readiness-checker.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,IAAI,EACJ,eAAe,EAIhB,MAAM,mBAAmB,CAAC;AA4K3B,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,eAAe,CAAC,CA+D1B"}
|