@ripla/godd-mcp 1.0.1 → 1.0.2-canary.2
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/README.md +2 -2
- package/dist/godd.cjs +116 -111
- package/dist/godd.js +114 -17
- package/dist/index.js +1 -1
- package/notes-api/app/config.py +24 -3
- package/notes-api/app/database.py +10 -4
- package/notes-api/app/routers/comments.py +1 -0
- package/notes-api/app/routers/files.py +0 -1
- package/notes-api/app/routers/settings.py +4 -2
- package/notes-api/app/routers/tree.py +10 -2
- package/notes-api/app/services/github_issues.py +1 -1
- package/notes-api/tests/test_config_ssl.py +28 -0
- package/notes-api/tests/test_issues.py +15 -4
- package/notes-api/tests/test_pr.py +0 -1
- package/notes-api/tests/test_pr_chain.py +2 -2
- package/notes-api/tests/test_tree.py +15 -3
- package/notes-app/src/components/IssueList.tsx +36 -16
- package/notes-app/vitest.config.ts +1 -0
- package/package.json +1 -1
- package/templates/agents/architect.hbs +4 -4
- package/templates/agents/auto-documenter.hbs +4 -4
- package/templates/agents/backend-developer.hbs +3 -3
- package/templates/agents/code-reviewer.hbs +2 -2
- package/templates/agents/database-developer.hbs +4 -4
- package/templates/agents/debug-specialist.hbs +2 -2
- package/templates/agents/environment-setup.hbs +1 -1
- package/templates/agents/frontend-developer.hbs +5 -5
- package/templates/agents/integration-qa.hbs +1 -1
- package/templates/agents/pr-writer.hbs +1 -1
- package/templates/agents/quality-lead.hbs +3 -3
- package/templates/agents/requirements-analyst.hbs +2 -2
- package/templates/agents/security-analyst.hbs +1 -1
- package/templates/agents/ssot-updater.hbs +3 -3
- package/templates/agents/technical-writer.hbs +3 -3
- package/templates/agents/unit-test-engineer.hbs +1 -1
- package/templates/prompts/agent-dev-workflow.hbs +1 -1
- package/templates/prompts/docs-init.hbs +41 -59
- package/templates/prompts/docs-update.hbs +10 -10
- package/templates/prompts/notes-deploy.hbs +2 -1
- package/templates/prompts/pr-create.hbs +2 -2
- package/templates/prompts/push-execute.hbs +2 -2
- package/templates/prompts/push.hbs +1 -1
- package/templates/prompts/requirements-to-business-flow.hbs +1 -1
- package/templates/prompts/specification-to-tickets.hbs +1 -1
- package/templates/prompts/submit.hbs +2 -2
- package/templates/prompts/sync.hbs +1 -1
package/dist/godd.js
CHANGED
|
@@ -32431,7 +32431,7 @@ var init_spec_to_tickets = __esm({
|
|
|
32431
32431
|
init_zod();
|
|
32432
32432
|
init_base();
|
|
32433
32433
|
specToTicketsToolName = "godd_spec_to_tickets";
|
|
32434
|
-
specToTicketsToolDescription = "\u4ED5\u69D8\u2192\u30C1\u30B1\u30C3\u30C8\u5909\u63DB\u3002Spec\uFF08docs/
|
|
32434
|
+
specToTicketsToolDescription = "\u4ED5\u69D8\u2192\u30C1\u30B1\u30C3\u30C8\u5909\u63DB\u3002Spec\uFF08docs/003_requirements/spec/\uFF09\u304B\u3089\u5B9F\u88C5\u53EF\u80FD\u306A\u30BF\u30B9\u30AF\u30EA\u30B9\u30C8\u3092\u751F\u6210\u3057\u3001\u5F71\u97FF\u30EC\u30A4\u30E4\u30FC\u30FB\u30C6\u30B9\u30C8\u89B3\u70B9\u30FB\u30EA\u30B9\u30AF\u3092\u4ED8\u4E0E\u3057\u307E\u3059\u3002";
|
|
32435
32435
|
specToTicketsToolInputSchema = external_exports.object({
|
|
32436
32436
|
specification: external_exports.string().max(2e5).optional().describe("\u5BFE\u8C61Spec\uFF08API/UI/DB/Feature/Usecase/Error Codes\uFF09"),
|
|
32437
32437
|
project_root: external_exports.string().max(1e3).optional().describe("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30EB\u30FC\u30C8\u30D1\u30B9\uFF08\u81EA\u52D5\u691C\u51FA\u7528\uFF09")
|
|
@@ -33896,7 +33896,10 @@ var init_install2 = __esm({
|
|
|
33896
33896
|
var notes_infra_exports = {};
|
|
33897
33897
|
__export(notes_infra_exports, {
|
|
33898
33898
|
PROVIDER_LABELS: () => PROVIDER_LABELS,
|
|
33899
|
-
|
|
33899
|
+
findNotesSourceDirs: () => findNotesSourceDirs,
|
|
33900
|
+
runNotesInfra: () => runNotesInfra,
|
|
33901
|
+
scaffoldNotesApplicationSources: () => scaffoldNotesApplicationSources,
|
|
33902
|
+
shouldExcludeNotesSourceEntry: () => shouldExcludeNotesSourceEntry
|
|
33900
33903
|
});
|
|
33901
33904
|
import * as readline3 from "node:readline";
|
|
33902
33905
|
import {
|
|
@@ -33905,7 +33908,9 @@ import {
|
|
|
33905
33908
|
writeFileSync as writeFileSync4,
|
|
33906
33909
|
readFileSync as readFileSync10,
|
|
33907
33910
|
readdirSync as readdirSync3,
|
|
33908
|
-
chmodSync as chmodSync2
|
|
33911
|
+
chmodSync as chmodSync2,
|
|
33912
|
+
copyFileSync as copyFileSync2,
|
|
33913
|
+
statSync as statSync2
|
|
33909
33914
|
} from "node:fs";
|
|
33910
33915
|
import { join as join9, dirname as dirname3, resolve as resolve3 } from "node:path";
|
|
33911
33916
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -33937,6 +33942,78 @@ function findTemplatesDir(provider) {
|
|
|
33937
33942
|
}
|
|
33938
33943
|
return null;
|
|
33939
33944
|
}
|
|
33945
|
+
function shouldExcludeNotesSourceEntry(entry, extraExclude) {
|
|
33946
|
+
if (entry === ".env") return true;
|
|
33947
|
+
if (entry.startsWith(".env.") && entry !== ".env.example") return true;
|
|
33948
|
+
if (entry.endsWith(".pyc") || entry.endsWith(".tsbuildinfo")) return true;
|
|
33949
|
+
return COMMON_NOTES_SOURCE_EXCLUDE.has(entry) || extraExclude.has(entry);
|
|
33950
|
+
}
|
|
33951
|
+
function hasNotesSourceMarkers(apiDir, appDir) {
|
|
33952
|
+
return existsSync10(join9(apiDir, "app", "main.py")) && existsSync10(join9(appDir, "package.json")) && existsSync10(join9(appDir, "src"));
|
|
33953
|
+
}
|
|
33954
|
+
function findNotesSourceDirs() {
|
|
33955
|
+
const pkgRoot = getPackageRoot();
|
|
33956
|
+
const candidates = [
|
|
33957
|
+
resolve3(pkgRoot, ".."),
|
|
33958
|
+
pkgRoot,
|
|
33959
|
+
dirname3(process.execPath),
|
|
33960
|
+
process.cwd()
|
|
33961
|
+
];
|
|
33962
|
+
for (const root of candidates) {
|
|
33963
|
+
const apiDir = join9(root, "notes-api");
|
|
33964
|
+
const appDir = join9(root, "notes-app");
|
|
33965
|
+
if (existsSync10(apiDir) && existsSync10(appDir) && hasNotesSourceMarkers(apiDir, appDir)) {
|
|
33966
|
+
return { apiDir, appDir };
|
|
33967
|
+
}
|
|
33968
|
+
}
|
|
33969
|
+
return null;
|
|
33970
|
+
}
|
|
33971
|
+
function copyDirRecursive(src, dest, exclude) {
|
|
33972
|
+
mkdirSync4(dest, { recursive: true });
|
|
33973
|
+
for (const entry of readdirSync3(src)) {
|
|
33974
|
+
if (shouldExcludeNotesSourceEntry(entry, exclude)) continue;
|
|
33975
|
+
const srcPath = join9(src, entry);
|
|
33976
|
+
const destPath = join9(dest, entry);
|
|
33977
|
+
if (statSync2(srcPath).isDirectory()) {
|
|
33978
|
+
copyDirRecursive(srcPath, destPath, exclude);
|
|
33979
|
+
} else {
|
|
33980
|
+
copyFileSync2(srcPath, destPath);
|
|
33981
|
+
}
|
|
33982
|
+
}
|
|
33983
|
+
}
|
|
33984
|
+
function scaffoldNotesApplicationSources(outputDir) {
|
|
33985
|
+
const notesDirs = findNotesSourceDirs();
|
|
33986
|
+
if (!notesDirs) {
|
|
33987
|
+
console.warn(
|
|
33988
|
+
"\n \u26A0 notes-api / notes-app \u306E\u540C\u68B1\u30BD\u30FC\u30B9\u304C\u898B\u3064\u304B\u3089\u306A\u3044\u305F\u3081\u3001\u30A2\u30D7\u30EA scaffold \u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F\u3002"
|
|
33989
|
+
);
|
|
33990
|
+
return [];
|
|
33991
|
+
}
|
|
33992
|
+
const scaffolded = [];
|
|
33993
|
+
const targets = [
|
|
33994
|
+
{
|
|
33995
|
+
label: "notes-api",
|
|
33996
|
+
src: notesDirs.apiDir,
|
|
33997
|
+
dest: join9(outputDir, "notes-api"),
|
|
33998
|
+
exclude: NOTES_API_SOURCE_EXCLUDE
|
|
33999
|
+
},
|
|
34000
|
+
{
|
|
34001
|
+
label: "notes-app",
|
|
34002
|
+
src: notesDirs.appDir,
|
|
34003
|
+
dest: join9(outputDir, "notes-app"),
|
|
34004
|
+
exclude: NOTES_APP_SOURCE_EXCLUDE
|
|
34005
|
+
}
|
|
34006
|
+
];
|
|
34007
|
+
console.log(`
|
|
34008
|
+
[\u30A2\u30D7\u30EA] Notes \u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u30BD\u30FC\u30B9\u3092\u540C\u671F: ${outputDir}`);
|
|
34009
|
+
for (const target of targets) {
|
|
34010
|
+
const action = existsSync10(target.dest) ? "\u66F4\u65B0" : "\u914D\u7F6E";
|
|
34011
|
+
copyDirRecursive(target.src, target.dest, target.exclude);
|
|
34012
|
+
scaffolded.push(target.label);
|
|
34013
|
+
console.log(` \u2713 ${target.label}/ \u3092${action}`);
|
|
34014
|
+
}
|
|
34015
|
+
return scaffolded;
|
|
34016
|
+
}
|
|
33940
34017
|
function loadProjectConfig(dir) {
|
|
33941
34018
|
const configPath = join9(dir, CONFIG_FILE_NAME);
|
|
33942
34019
|
if (!existsSync10(configPath)) return null;
|
|
@@ -34169,7 +34246,7 @@ async function runInfraWizard(rl2) {
|
|
|
34169
34246
|
console.log(`
|
|
34170
34247
|
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
34171
34248
|
\u2551 ripla Notes \u30A4\u30F3\u30D5\u30E9\u69CB\u7BC9\u30A6\u30A3\u30B6\u30FC\u30C9 \u2551
|
|
34172
|
-
\u2551 Terraform + GitHub Actions \u81EA\u52D5\u751F\u6210
|
|
34249
|
+
\u2551 \u30A2\u30D7\u30EA + Terraform + GitHub Actions \u81EA\u52D5\u751F\u6210 \u2551
|
|
34173
34250
|
\u2551 \u2551
|
|
34174
34251
|
\u2551 \u5BFE\u5FDC\u30D7\u30ED\u30D0\u30A4\u30C0: \u2551
|
|
34175
34252
|
\u2551 AWS / GCP / Azure / Vercel\xD7Railway \u2551
|
|
@@ -34268,6 +34345,7 @@ function generateFiles(config2) {
|
|
|
34268
34345
|
} else {
|
|
34269
34346
|
generateGenericFiles(terraformDir, ghaDir, config2, context);
|
|
34270
34347
|
}
|
|
34348
|
+
scaffoldNotesApplicationSources(config2.outputDir);
|
|
34271
34349
|
saveProjectConfig(config2);
|
|
34272
34350
|
console.log(
|
|
34273
34351
|
`
|
|
@@ -34446,6 +34524,9 @@ function printNextSteps(config2) {
|
|
|
34446
34524
|
\u2551 - admin-credentials \u2551
|
|
34447
34525
|
\u2551 - github-token (\u4EFB\u610F) \u2551
|
|
34448
34526
|
\u2551 \u2551
|
|
34527
|
+
\u2551 5. \u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3092\u30C7\u30D7\u30ED\u30A4 \u2551
|
|
34528
|
+
\u2551 godd notes deploy -y \u2551
|
|
34529
|
+
\u2551 \u2551
|
|
34449
34530
|
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
34450
34531
|
`);
|
|
34451
34532
|
break;
|
|
@@ -34526,7 +34607,7 @@ function printNextSteps(config2) {
|
|
|
34526
34607
|
async function runNotesInfra(args) {
|
|
34527
34608
|
if (args.includes("--help") || args.includes("-h")) {
|
|
34528
34609
|
console.log(`
|
|
34529
|
-
GoDD Notes Infra \u2014 \u30DE\u30EB\u30C1\u30AF\u30E9\u30A6\u30C9 \u30A4\u30F3\u30D5\u30E9\u81EA\u52D5\u751F\u6210
|
|
34610
|
+
GoDD Notes Infra \u2014 \u30A2\u30D7\u30EA + \u30DE\u30EB\u30C1\u30AF\u30E9\u30A6\u30C9 \u30A4\u30F3\u30D5\u30E9\u81EA\u52D5\u751F\u6210
|
|
34530
34611
|
|
|
34531
34612
|
Usage: godd notes infra [options]
|
|
34532
34613
|
|
|
@@ -34560,7 +34641,7 @@ Options:
|
|
|
34560
34641
|
rl2.close();
|
|
34561
34642
|
}
|
|
34562
34643
|
}
|
|
34563
|
-
var import_handlebars2, PROVIDER_LABELS, CONFIG_FILE_NAME;
|
|
34644
|
+
var import_handlebars2, PROVIDER_LABELS, COMMON_NOTES_SOURCE_EXCLUDE, NOTES_APP_SOURCE_EXCLUDE, NOTES_API_SOURCE_EXCLUDE, CONFIG_FILE_NAME;
|
|
34564
34645
|
var init_notes_infra = __esm({
|
|
34565
34646
|
"src/cli/notes-infra.ts"() {
|
|
34566
34647
|
"use strict";
|
|
@@ -34572,6 +34653,22 @@ var init_notes_infra = __esm({
|
|
|
34572
34653
|
azure: "Azure (Container Apps + Blob Storage + PostgreSQL)",
|
|
34573
34654
|
"vercel-railway": "Vercel \xD7 Railway (PaaS)"
|
|
34574
34655
|
};
|
|
34656
|
+
COMMON_NOTES_SOURCE_EXCLUDE = /* @__PURE__ */ new Set([
|
|
34657
|
+
".git",
|
|
34658
|
+
"coverage",
|
|
34659
|
+
"dist",
|
|
34660
|
+
"node_modules"
|
|
34661
|
+
]);
|
|
34662
|
+
NOTES_APP_SOURCE_EXCLUDE = /* @__PURE__ */ new Set([
|
|
34663
|
+
"playwright-report",
|
|
34664
|
+
"test-results"
|
|
34665
|
+
]);
|
|
34666
|
+
NOTES_API_SOURCE_EXCLUDE = /* @__PURE__ */ new Set([
|
|
34667
|
+
".pytest_cache",
|
|
34668
|
+
".ruff_cache",
|
|
34669
|
+
".venv",
|
|
34670
|
+
"__pycache__"
|
|
34671
|
+
]);
|
|
34575
34672
|
CONFIG_FILE_NAME = ".godd-notes-infra.json";
|
|
34576
34673
|
}
|
|
34577
34674
|
});
|
|
@@ -34597,9 +34694,9 @@ import {
|
|
|
34597
34694
|
mkdirSync as mkdirSync5,
|
|
34598
34695
|
writeFileSync as writeFileSync5,
|
|
34599
34696
|
readFileSync as readFileSync11,
|
|
34600
|
-
copyFileSync as
|
|
34697
|
+
copyFileSync as copyFileSync3,
|
|
34601
34698
|
readdirSync as readdirSync4,
|
|
34602
|
-
statSync as
|
|
34699
|
+
statSync as statSync3
|
|
34603
34700
|
} from "node:fs";
|
|
34604
34701
|
import { join as join10, dirname as dirname4, resolve as resolve4 } from "node:path";
|
|
34605
34702
|
import * as childProcess from "node:child_process";
|
|
@@ -34646,17 +34743,17 @@ function findNotesDir() {
|
|
|
34646
34743
|
}
|
|
34647
34744
|
return null;
|
|
34648
34745
|
}
|
|
34649
|
-
function
|
|
34746
|
+
function copyDirRecursive2(src, dest) {
|
|
34650
34747
|
mkdirSync5(dest, { recursive: true });
|
|
34651
34748
|
for (const entry of readdirSync4(src)) {
|
|
34652
34749
|
if (COPY_EXCLUDE.has(entry)) continue;
|
|
34653
34750
|
if (entry.endsWith(".pyc") || entry.endsWith(".tsbuildinfo")) continue;
|
|
34654
34751
|
const srcPath = join10(src, entry);
|
|
34655
34752
|
const destPath = join10(dest, entry);
|
|
34656
|
-
if (
|
|
34657
|
-
|
|
34753
|
+
if (statSync3(srcPath).isDirectory()) {
|
|
34754
|
+
copyDirRecursive2(srcPath, destPath);
|
|
34658
34755
|
} else {
|
|
34659
|
-
|
|
34756
|
+
copyFileSync3(srcPath, destPath);
|
|
34660
34757
|
}
|
|
34661
34758
|
}
|
|
34662
34759
|
}
|
|
@@ -34961,9 +35058,9 @@ async function deploy(config2) {
|
|
|
34961
35058
|
[1/5] \u30C7\u30D7\u30ED\u30A4\u5148\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u6E96\u5099: ${config2.deployDir}`);
|
|
34962
35059
|
mkdirSync5(config2.deployDir, { recursive: true });
|
|
34963
35060
|
console.log("[2/5] notes-api \u3092\u30B3\u30D4\u30FC...");
|
|
34964
|
-
|
|
35061
|
+
copyDirRecursive2(apiDir, join10(config2.deployDir, "notes-api"));
|
|
34965
35062
|
console.log("[3/5] notes-app \u3092\u30B3\u30D4\u30FC...");
|
|
34966
|
-
|
|
35063
|
+
copyDirRecursive2(appDir, join10(config2.deployDir, "notes-app"));
|
|
34967
35064
|
console.log("[4/5] docker-compose.yml \u3068 .env \u3092\u751F\u6210...");
|
|
34968
35065
|
const composeContent = readFileSync11(composeTemplate, "utf-8");
|
|
34969
35066
|
writeFileSync5(join10(config2.deployDir, "docker-compose.yml"), composeContent, "utf-8");
|
|
@@ -35195,7 +35292,7 @@ Usage: godd notes <command> [options]
|
|
|
35195
35292
|
Commands:
|
|
35196
35293
|
compose \u30ED\u30FC\u30AB\u30EB Docker Compose \u3067\u30C7\u30D7\u30ED\u30A4
|
|
35197
35294
|
deploy AWS \u306B\u76F4\u63A5\u30C7\u30D7\u30ED\u30A4\uFF08ECR push \u2192 ECS \u66F4\u65B0 \u2192 S3 sync \u2192 CloudFront \u7121\u52B9\u5316\uFF09
|
|
35198
|
-
infra \u30AF\u30E9\u30A6\u30C9\u30A4\u30F3\u30D5\u30E9\u3092 Terraform + GitHub Actions \u3067\u69CB\u7BC9
|
|
35295
|
+
infra Notes \u30A2\u30D7\u30EA\u96DB\u5F62\u3068\u30AF\u30E9\u30A6\u30C9\u30A4\u30F3\u30D5\u30E9\u3092 Terraform + GitHub Actions \u3067\u69CB\u7BC9
|
|
35199
35296
|
status \u30C7\u30D7\u30ED\u30A4\u72B6\u614B\u30FB\u30D8\u30EB\u30B9\u30C1\u30A7\u30C3\u30AF\u3092\u78BA\u8A8D
|
|
35200
35297
|
|
|
35201
35298
|
compose \u30AA\u30D7\u30B7\u30E7\u30F3:
|
|
@@ -35222,7 +35319,7 @@ Examples:
|
|
|
35222
35319
|
godd notes compose --config config.json \u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u304B\u3089\u30C7\u30D7\u30ED\u30A4
|
|
35223
35320
|
godd notes deploy AWS \u306B\u30C7\u30D7\u30ED\u30A4\uFF08\u5BFE\u8A71\u5F62\u5F0F\u3067\u78BA\u8A8D\uFF09
|
|
35224
35321
|
godd notes deploy -y \u78BA\u8A8D\u306A\u3057\u3067 AWS \u306B\u30C7\u30D7\u30ED\u30A4
|
|
35225
|
-
godd notes infra Terraform + CI/CD \u30D5\u30A1\u30A4\u30EB\u751F\u6210
|
|
35322
|
+
godd notes infra \u30A2\u30D7\u30EA\u96DB\u5F62 + Terraform + CI/CD \u30D5\u30A1\u30A4\u30EB\u751F\u6210
|
|
35226
35323
|
godd notes status \u30C7\u30D7\u30ED\u30A4\u72B6\u614B\u3092\u78BA\u8A8D
|
|
35227
35324
|
|
|
35228
35325
|
AI \u9023\u643A:
|
|
@@ -35459,7 +35556,7 @@ Examples:
|
|
|
35459
35556
|
godd init \u73FE\u5728\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7
|
|
35460
35557
|
godd notes compose \u30ED\u30FC\u30AB\u30EB Docker Compose \u3067\u30C7\u30D7\u30ED\u30A4
|
|
35461
35558
|
godd notes deploy AWS \u306B\u30C7\u30D7\u30ED\u30A4\uFF08ECR/ECS/S3/CloudFront\uFF09
|
|
35462
|
-
godd notes infra \u30AF\u30E9\u30A6\u30C9\u30A4\u30F3\u30D5\u30E9\u3092\u5BFE\u8A71\u5F62\u5F0F\u3067\u69CB\u7BC9
|
|
35559
|
+
godd notes infra Notes \u30A2\u30D7\u30EA\u96DB\u5F62\u3068\u30AF\u30E9\u30A6\u30C9\u30A4\u30F3\u30D5\u30E9\u3092\u5BFE\u8A71\u5F62\u5F0F\u3067\u69CB\u7BC9
|
|
35463
35560
|
godd version \u30D0\u30FC\u30B8\u30E7\u30F3\u78BA\u8A8D
|
|
35464
35561
|
`);
|
|
35465
35562
|
}
|
package/dist/index.js
CHANGED
|
@@ -197,7 +197,7 @@ ${t.report_type}`),j(e,"analyze-report",r.join(`
|
|
|
197
197
|
|
|
198
198
|
`),t.project_root)}var kS="godd_req_to_flow",wS="\u8981\u6C42\u2192\u30D3\u30B8\u30CD\u30B9\u30D5\u30ED\u30FC\u5909\u63DB\u3002\u81EA\u7136\u8A00\u8A9E\u306E\u8981\u6C42\u304B\u3089As-Is/To-Be\u30D5\u30ED\u30FC\u3068\u30E6\u30FC\u30B9\u30B1\u30FC\u30B9\u3092\u751F\u6210\u3057\u307E\u3059\u3002",SS=m.object({requirements:m.string().max(2e5).optional().describe("\u8981\u6C42\u5185\u5BB9\uFF08\u81EA\u7136\u8A00\u8A9E\uFF09"),project_root:m.string().max(1e3).optional().describe("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30EB\u30FC\u30C8\u30D1\u30B9\uFF08\u81EA\u52D5\u691C\u51FA\u7528\uFF09")});function TS(e,t){return j(e,"requirements-to-business-flow",`## \u8981\u6C42
|
|
199
199
|
${t.requirements??"(Chat \u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304B\u3089\u5224\u65AD)"}`,t.project_root)}var $S="godd_req_to_tickets",PS="\u8981\u6C42\u2192\u30C1\u30B1\u30C3\u30C8\u5206\u89E3\u3002\u8981\u6C42\u3092\u5B9F\u88C5\u30BF\u30B9\u30AF\uFF08\u30C1\u30B1\u30C3\u30C8\uFF09\u306B\u5206\u89E3\u3057\u3001\u4F9D\u5B58\u95A2\u4FC2\u3068\u691C\u8A3C\u65B9\u6CD5\u3092\u542B\u3081\u3066\u4E00\u89A7\u5316\u3057\u307E\u3059\u3002",ES=m.object({requirements:m.string().max(2e5).optional().describe("\u8981\u6C42\u5185\u5BB9\uFF08\u81EA\u7136\u8A00\u8A9E\uFF09"),project_root:m.string().max(1e3).optional().describe("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30EB\u30FC\u30C8\u30D1\u30B9\uFF08\u81EA\u52D5\u691C\u51FA\u7528\uFF09")});function zS(e,t){return j(e,"requirements-to-tickets",`## \u8981\u6C42
|
|
200
|
-
${t.requirements??"(Chat \u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304B\u3089\u5224\u65AD)"}`,t.project_root)}var CS="godd_spec_to_tickets",IS="\u4ED5\u69D8\u2192\u30C1\u30B1\u30C3\u30C8\u5909\u63DB\u3002Spec\uFF08docs/
|
|
200
|
+
${t.requirements??"(Chat \u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304B\u3089\u5224\u65AD)"}`,t.project_root)}var CS="godd_spec_to_tickets",IS="\u4ED5\u69D8\u2192\u30C1\u30B1\u30C3\u30C8\u5909\u63DB\u3002Spec\uFF08docs/003_requirements/spec/\uFF09\u304B\u3089\u5B9F\u88C5\u53EF\u80FD\u306A\u30BF\u30B9\u30AF\u30EA\u30B9\u30C8\u3092\u751F\u6210\u3057\u3001\u5F71\u97FF\u30EC\u30A4\u30E4\u30FC\u30FB\u30C6\u30B9\u30C8\u89B3\u70B9\u30FB\u30EA\u30B9\u30AF\u3092\u4ED8\u4E0E\u3057\u307E\u3059\u3002",RS=m.object({specification:m.string().max(2e5).optional().describe("\u5BFE\u8C61Spec\uFF08API/UI/DB/Feature/Usecase/Error Codes\uFF09"),project_root:m.string().max(1e3).optional().describe("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30EB\u30FC\u30C8\u30D1\u30B9\uFF08\u81EA\u52D5\u691C\u51FA\u7528\uFF09")});function AS(e,t){return j(e,"specification-to-tickets",`## \u5BFE\u8C61Spec
|
|
201
201
|
${t.specification??"(Chat \u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304B\u3089\u5224\u65AD)"}`,t.project_root)}var OS="godd_install",NS="\u30C4\u30FC\u30EB/MCP\u30B5\u30FC\u30D0\u30FC\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3002\u6307\u5B9A\u3055\u308C\u305F\u30C4\u30FC\u30EB\u306E\u74B0\u5883\u306B\u9069\u3057\u305F\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u624B\u9806\u30FB\u8A2D\u5B9A\u30FB\u758E\u901A\u78BA\u8A8D\u3092\u6848\u5185\u3057\u307E\u3059\u3002",jS=m.object({target:m.string().max(1e3).describe("\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u5BFE\u8C61\u306E\u30C4\u30FC\u30EB/MCP\u540D\uFF08\u4F8B: context7, chrome-devtools-mcp, markitdown-mcp, mcp-ocr, serena, vibe-odf-read-mcp\uFF09"),project_root:m.string().max(1e3).optional().describe("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30EB\u30FC\u30C8\u30D1\u30B9\uFF08\u81EA\u52D5\u691C\u51FA\u7528\uFF09")});function MS(e,t){return j(e,"install",`## \u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u5BFE\u8C61
|
|
202
202
|
${t.target}`,t.project_root)}var DS="godd_docs_init",LS="\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306B docs/ \u30D5\u30A9\u30EB\u30C0\u69CB\u9020\u3092\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304B\u3089\u751F\u6210\u3057\u307E\u3059\u3002ripla Notes \u3067\u95B2\u89A7\u30FB\u7DE8\u96C6\u53EF\u80FD\u306A MD/CSV/drawio \u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u306E\u521D\u671F\u69CB\u9020\u3092\u4F5C\u6210\u3057\u307E\u3059\u3002",ZS=m.object({project_name:m.string().max(1e3).optional().describe("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D"),roles:m.array(m.string().max(1e3)).optional().describe("\u30ED\u30FC\u30EB\u540D\u4E00\u89A7\uFF08\u4F8B: \u7BA1\u7406\u8005, \u904B\u55B6\u8005, \u5229\u7528\u8005\uFF09"),screens:m.array(m.string().max(1e3)).optional().describe("\u753B\u9762ID\u4E00\u89A7\uFF08\u4F8B: SC-OP-01, SC-US-01\uFF09"),project_root:m.string().max(1e3).optional().describe("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30EB\u30FC\u30C8\u30D1\u30B9\uFF08\u81EA\u52D5\u691C\u51FA\u7528\uFF09")});function qS(e,t){let r=[`## \u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D
|
|
203
203
|
${t.project_name??"(\u81EA\u52D5\u691C\u51FA)"}`];return t.roles?.length&&r.push(`## \u30ED\u30FC\u30EB
|
package/notes-api/app/config.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import re
|
|
7
|
+
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
|
7
8
|
|
|
8
9
|
from pydantic import AliasChoices, Field, model_validator
|
|
9
10
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
@@ -11,6 +12,24 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
11
12
|
_logger = logging.getLogger(__name__)
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
def _remove_query_param(url: str, key: str) -> str:
|
|
16
|
+
parts = urlsplit(url)
|
|
17
|
+
query = urlencode(
|
|
18
|
+
[(k, v) for k, v in parse_qsl(parts.query, keep_blank_values=True) if k != key],
|
|
19
|
+
doseq=True,
|
|
20
|
+
)
|
|
21
|
+
return urlunsplit((parts.scheme, parts.netloc, parts.path, query, parts.fragment))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _set_query_param(url: str, key: str, value: str) -> str:
|
|
25
|
+
parts = urlsplit(url)
|
|
26
|
+
query_items = [
|
|
27
|
+
(k, v) for k, v in parse_qsl(parts.query, keep_blank_values=True) if k != key
|
|
28
|
+
]
|
|
29
|
+
query = urlencode([*query_items, (key, value)], doseq=True)
|
|
30
|
+
return urlunsplit((parts.scheme, parts.netloc, parts.path, query, parts.fragment))
|
|
31
|
+
|
|
32
|
+
|
|
14
33
|
class Settings(BaseSettings):
|
|
15
34
|
"""ripla Notes API settings."""
|
|
16
35
|
|
|
@@ -72,7 +91,10 @@ class Settings(BaseSettings):
|
|
|
72
91
|
def async_database_url(self) -> str:
|
|
73
92
|
"""Async DB URL for SQLAlchemy (asyncpg driver)."""
|
|
74
93
|
if self.database_url:
|
|
75
|
-
|
|
94
|
+
url = re.sub(r"^postgres(ql)?://", "postgresql+asyncpg://", self.database_url)
|
|
95
|
+
if self.database_ssl:
|
|
96
|
+
return _remove_query_param(url, "sslmode")
|
|
97
|
+
return url
|
|
76
98
|
return (
|
|
77
99
|
f"postgresql+asyncpg://{self.db_user}:{self.db_password}"
|
|
78
100
|
f"@{self.db_host}:{self.db_port}/{self.db_name}"
|
|
@@ -89,8 +111,7 @@ class Settings(BaseSettings):
|
|
|
89
111
|
f"@{self.db_host}:{self.db_port}/{self.db_name}"
|
|
90
112
|
)
|
|
91
113
|
if self.database_ssl:
|
|
92
|
-
|
|
93
|
-
url = f"{url}{sep}sslmode=require"
|
|
114
|
+
url = _set_query_param(url, "sslmode", "require")
|
|
94
115
|
return url
|
|
95
116
|
|
|
96
117
|
@property
|
|
@@ -7,10 +7,16 @@ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_asyn
|
|
|
7
7
|
|
|
8
8
|
from app.config import settings
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
|
|
11
|
+
def _build_async_connect_args(database_ssl: bool) -> dict[str, Any]:
|
|
12
|
+
if not database_ssl:
|
|
13
|
+
return {}
|
|
14
|
+
# asyncpg: "require" = encrypt connection without certificate verification
|
|
15
|
+
# (equivalent to psycopg2's sslmode=require, works with AWS RDS / GCP Cloud SQL / Azure)
|
|
16
|
+
return {"ssl": "require"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_async_connect_args = _build_async_connect_args(settings.database_ssl)
|
|
14
20
|
|
|
15
21
|
engine = create_async_engine(
|
|
16
22
|
settings.async_database_url,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Settings router."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
from fastapi import APIRouter, Depends, HTTPException
|
|
7
8
|
from fastapi.encoders import jsonable_encoder
|
|
@@ -72,9 +73,10 @@ async def update_setting(
|
|
|
72
73
|
"""Update setting (admin only). Only whitelisted keys are accepted."""
|
|
73
74
|
validator = ALLOWED_SETTINGS.get(key)
|
|
74
75
|
if validator is None:
|
|
76
|
+
allowed_keys = ", ".join(sorted(ALLOWED_SETTINGS))
|
|
75
77
|
raise HTTPException(
|
|
76
78
|
status_code=400,
|
|
77
|
-
detail=f"Unknown setting key: {key}. Allowed keys: {
|
|
79
|
+
detail=f"Unknown setting key: {key}. Allowed keys: {allowed_keys}",
|
|
78
80
|
)
|
|
79
81
|
if not validator(body.value):
|
|
80
82
|
raise HTTPException(
|
|
@@ -21,7 +21,12 @@ async def get_tree(
|
|
|
21
21
|
):
|
|
22
22
|
"""Get file tree. Pass *ref* to read a specific branch. Mock mode when GITHUB_TOKEN not set."""
|
|
23
23
|
if settings.is_mock_mode():
|
|
24
|
-
return {
|
|
24
|
+
return {
|
|
25
|
+
"mode": "mock",
|
|
26
|
+
"source": "mock",
|
|
27
|
+
"reason": "GITHUB_TOKEN が未設定です",
|
|
28
|
+
"tree": get_mock_tree(),
|
|
29
|
+
}
|
|
25
30
|
|
|
26
31
|
try:
|
|
27
32
|
config = get_github_config()
|
|
@@ -37,7 +42,10 @@ async def get_tree(
|
|
|
37
42
|
"Organization の承認が必要な場合があります。"
|
|
38
43
|
)
|
|
39
44
|
elif code == 404:
|
|
40
|
-
detail =
|
|
45
|
+
detail = (
|
|
46
|
+
"リポジトリまたはパスが見つかりません: "
|
|
47
|
+
f"{settings.github_owner}/{settings.github_repo}"
|
|
48
|
+
)
|
|
41
49
|
else:
|
|
42
50
|
detail = f"GitHub API エラー (HTTP {code})"
|
|
43
51
|
raise HTTPException(
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import logging
|
|
6
6
|
|
|
7
7
|
from app.http_client import get_github_client
|
|
8
|
-
from app.services.github import parse_link_next, resolve_token
|
|
8
|
+
from app.services.github import _auth_headers, parse_link_next, resolve_token
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""DATABASE_SSL / DB_SSL settings for managed Postgres (e.g. AWS RDS)."""
|
|
2
2
|
|
|
3
3
|
from app.config import Settings
|
|
4
|
+
from app.database import _build_async_connect_args
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def test_database_ssl_defaults_false(monkeypatch):
|
|
@@ -33,3 +34,30 @@ def test_sync_database_url_appends_sslmode_when_ssl_enabled():
|
|
|
33
34
|
database_ssl=True,
|
|
34
35
|
)
|
|
35
36
|
assert "sslmode=require" in s.sync_database_url
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_sync_database_url_overrides_existing_sslmode():
|
|
40
|
+
s = Settings(
|
|
41
|
+
database_url="postgresql://u:p@db.example.com:5432/app?sslmode=disable",
|
|
42
|
+
database_ssl=True,
|
|
43
|
+
)
|
|
44
|
+
assert s.sync_database_url.count("sslmode=require") == 1
|
|
45
|
+
assert "sslmode=disable" not in s.sync_database_url
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_async_database_url_removes_sslmode_when_connect_args_handle_ssl():
|
|
49
|
+
s = Settings(
|
|
50
|
+
database_url="postgresql://u:p@db.example.com:5432/app?sslmode=require&application_name=notes",
|
|
51
|
+
database_ssl=True,
|
|
52
|
+
)
|
|
53
|
+
assert s.async_database_url == (
|
|
54
|
+
"postgresql+asyncpg://u:p@db.example.com:5432/app?application_name=notes"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_async_database_connect_args_require_ssl_without_cert_verification():
|
|
59
|
+
assert _build_async_connect_args(database_ssl=True) == {"ssl": "require"}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_async_database_connect_args_empty_when_ssl_disabled():
|
|
63
|
+
assert _build_async_connect_args(database_ssl=False) == {}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from unittest.mock import AsyncMock, patch
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
|
-
import pytest
|
|
7
6
|
from httpx import AsyncClient
|
|
8
7
|
|
|
9
8
|
from tests.conftest import auth_headers
|
|
@@ -46,7 +45,11 @@ class TestListIssues:
|
|
|
46
45
|
):
|
|
47
46
|
mock_settings.is_mock_mode.return_value = False
|
|
48
47
|
mock_config.return_value = {"token": "t", "owner": "o", "repo": "r", "branch": "main"}
|
|
49
|
-
mock_list.side_effect = httpx.HTTPStatusError(
|
|
48
|
+
mock_list.side_effect = httpx.HTTPStatusError(
|
|
49
|
+
"",
|
|
50
|
+
request=mock_resp.request,
|
|
51
|
+
response=mock_resp,
|
|
52
|
+
)
|
|
50
53
|
resp = await client.get("/api/issues", headers=auth_headers(admin_token))
|
|
51
54
|
assert resp.status_code == 401
|
|
52
55
|
|
|
@@ -81,7 +84,11 @@ class TestGetIssue:
|
|
|
81
84
|
):
|
|
82
85
|
mock_settings.is_mock_mode.return_value = False
|
|
83
86
|
mock_config.return_value = {"token": "t", "owner": "o", "repo": "r", "branch": "main"}
|
|
84
|
-
mock_get.side_effect = httpx.HTTPStatusError(
|
|
87
|
+
mock_get.side_effect = httpx.HTTPStatusError(
|
|
88
|
+
"",
|
|
89
|
+
request=mock_resp.request,
|
|
90
|
+
response=mock_resp,
|
|
91
|
+
)
|
|
85
92
|
resp = await client.get("/api/issues/999", headers=auth_headers(admin_token))
|
|
86
93
|
assert resp.status_code == 404
|
|
87
94
|
|
|
@@ -176,7 +183,11 @@ class TestUpdateIssue:
|
|
|
176
183
|
):
|
|
177
184
|
mock_settings.is_mock_mode.return_value = False
|
|
178
185
|
mock_config.return_value = {"token": "t", "owner": "o", "repo": "r", "branch": "main"}
|
|
179
|
-
mock_update.side_effect = httpx.HTTPStatusError(
|
|
186
|
+
mock_update.side_effect = httpx.HTTPStatusError(
|
|
187
|
+
"",
|
|
188
|
+
request=mock_resp.request,
|
|
189
|
+
response=mock_resp,
|
|
190
|
+
)
|
|
180
191
|
resp = await client.patch(
|
|
181
192
|
"/api/issues/999",
|
|
182
193
|
json={"title": "Does not exist"},
|
|
@@ -649,7 +649,7 @@ class TestRenameOnWorkingBranch:
|
|
|
649
649
|
|
|
650
650
|
with (
|
|
651
651
|
patch(
|
|
652
|
-
"app.services.
|
|
652
|
+
"app.services.pr_chain.get_business_date",
|
|
653
653
|
new_callable=AsyncMock, return_value="2026-04-17",
|
|
654
654
|
),
|
|
655
655
|
patch(
|
|
@@ -714,7 +714,7 @@ class TestRenameOnWorkingBranch:
|
|
|
714
714
|
|
|
715
715
|
with (
|
|
716
716
|
patch(
|
|
717
|
-
"app.services.
|
|
717
|
+
"app.services.pr_chain.get_business_date",
|
|
718
718
|
new_callable=AsyncMock, return_value="2026-04-17",
|
|
719
719
|
),
|
|
720
720
|
patch(
|
|
@@ -68,7 +68,11 @@ class TestTree:
|
|
|
68
68
|
mock_config.return_value = {
|
|
69
69
|
"token": "t", "owner": "o", "repo": "r", "branch": "main", "docs_path": "docs",
|
|
70
70
|
}
|
|
71
|
-
mock_fetch.side_effect = httpx.HTTPStatusError(
|
|
71
|
+
mock_fetch.side_effect = httpx.HTTPStatusError(
|
|
72
|
+
"",
|
|
73
|
+
request=mock_resp.request,
|
|
74
|
+
response=mock_resp,
|
|
75
|
+
)
|
|
72
76
|
resp = await client.get("/api/tree", headers=self._headers)
|
|
73
77
|
assert resp.status_code == 502
|
|
74
78
|
|
|
@@ -84,7 +88,11 @@ class TestTree:
|
|
|
84
88
|
mock_config.return_value = {
|
|
85
89
|
"token": "t", "owner": "o", "repo": "r", "branch": "main", "docs_path": "docs",
|
|
86
90
|
}
|
|
87
|
-
mock_fetch.side_effect = httpx.HTTPStatusError(
|
|
91
|
+
mock_fetch.side_effect = httpx.HTTPStatusError(
|
|
92
|
+
"",
|
|
93
|
+
request=mock_resp.request,
|
|
94
|
+
response=mock_resp,
|
|
95
|
+
)
|
|
88
96
|
resp = await client.get("/api/tree", headers=self._headers)
|
|
89
97
|
assert resp.status_code == 502
|
|
90
98
|
assert "権限" in resp.json()["detail"]
|
|
@@ -124,7 +132,11 @@ class TestTree:
|
|
|
124
132
|
mock_config.return_value = {
|
|
125
133
|
"token": "t", "owner": "o", "repo": "r", "branch": "main", "docs_path": "docs",
|
|
126
134
|
}
|
|
127
|
-
mock_fetch.side_effect = httpx.HTTPStatusError(
|
|
135
|
+
mock_fetch.side_effect = httpx.HTTPStatusError(
|
|
136
|
+
"",
|
|
137
|
+
request=mock_resp.request,
|
|
138
|
+
response=mock_resp,
|
|
139
|
+
)
|
|
128
140
|
resp = await client.get("/api/tree", headers=self._headers)
|
|
129
141
|
assert resp.status_code == 502
|
|
130
142
|
assert "見つかりません" in resp.json()["detail"]
|